1.线程使用
1.1线程的休眠打印字幕
1.线程休眠演示打印电影字幕
1.2多线程 VS 单线程性能
1.单线程:
public class ThreadDemo2 {
private final static int COUNT=10;
public static void main(String[] args) {
long stime=System.currentTimeMillis();
singleThread();
long etime=System.currentTimeMillis();
System.out.println("执行时间:"+(etime-stime));
}
private static void singleThread() {
for (int i = 0; i <COUNT ; i++) {
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
2.多线程:
import java.time.LocalTime;
public class ThreadDemo2 {
private final static int COUNT=10;
public static void main(String[] args) {
long stime=System.currentTimeMillis();
multiThread();
long etime=System.currentTimeMillis();
System.out.println("执行时间:"+(etime-stime));
}
private static void multiThread() {
Thread t=new Thread(()->{
for (int i = 0; i <COUNT/2 ; i++) {
try{
Thread.sleep(1000);
System.out.println("新线程:执行了for循环:"+ LocalTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
for (int i = 0; i <COUNT/2 ; i++) {
try {
Thread.sleep(1000);
System.out.println("主线程:执行了for循环:"+LocalTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void singleThread() {
for (int i = 0; i <COUNT ; i++) {
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
1.3线程创建方式
1.3.1继承Thread(一种)
1.创建MyThread线程继承Thread来创建线程:
public class ThreadDemo3 {
public static void main(String[] args) {
Thread mainThread=Thread.currentThread();
System.out.println("线程名称:"+mainThread.getName());
Thread thread=new MyThread();
thread.start();
}
}
class MyThread extends Thread{
@Override
public void run(){
System.out.println("你好,编程");
Thread thread=Thread.currentThread();
System.out.println("线程的名称:"+thread.getName());
}
}
2.使用jconsole观察线程 因为java是单继承,继承了Thread就不能继承其他的类了,然而java可以实现多接口。 3启动线程-start方法 上面我们看到了如何通过覆写run方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了. (1)覆写run方法是提供给线程要做的失去的指令清单。 (2)线程对象可以认为是把李四,小明叫过来了。 (3)而调用start()方法,就是喊一声:“行动起来!”,线程才真正独立去执行了。 调用start方法,才真的在操作系统的底层创建出了一个线程。
4.start方法和run方法区别?(重点) (1)启动线程用的是start方法,执行start方法才会新创建线程执行。
public class ThreadDemo3 {
public static void main(String[] args) {
Thread mainThread=Thread.currentThread();
System.out.println("线程名称:"+mainThread.getName());
Thread thread=new MyThread();
thread.start();
}
}
class MyThread extends Thread{
@Override
public void run(){
System.out.println("你好,编程");
Thread thread=Thread.currentThread();
System.out.println("线程的名称:"+thread.getName());
}
}
(2)执行run方法就是使用main线程调用一个普通方法而已: (3)综上run方法和start方法有三个区别: 区别一: run方法和start方法的第一个区别是:调用start方法时真正开启一个线程来执行任务,调用run方法相当于执行普通方法run,并不会开启新线程。 区别二: run方法也叫做线程体,它里面包含了具体要执行的业务代码,当调用run方法时,会立即执行run方法中的代码(如果当前线程时间片未用完);而调用start方法时,是启动一个线程并将线程的状态设置就绪状态。也就是说调用start方法,并不会立即执行。 区别三: 因为run方法时普通方法,而普通方法可以被多次调用,所以run方法可以被多次调用,而start方法是创建新线程来执行任务,因为线程只能被创建一次,线程的状态是不可逆的,所以它们的第三个区别是:run方法可以被多次调用,而start方法只能被调用一次。
为什么start不能被重复调用? 要找到这个问题的答案,我们就要查看start方法的实现源码: 从start源码实现的第一行,我们就可以得到这个问题的答案,因为start方法在执行时,会先判断当前线程的状态是不是等于0,也就是是否为新建状态NEW,如果不等于新建状态,那么就会抛出"IIIegalThreadStateException"非法线程状态异常,这就是线程的start方法不能被重复调用的原因。它的执行过程为:当线程调用了第一个start方法之后,线程的状态就会从新建状态NEW变为就绪状态RUNNABLE,此时再次调用start方法,JVM就会判断出当前的线程已经不等于新建状态,从而抛出IIIegalThreadStateException非法线程状态异常。
总结:
5.继承Thread新建线程的缺点: java语言是单继承,如果继承了Thread之后,就不能继承其他类。
1.3.2实现Runnable接口(四种)
1.方式一:实现Runnable接口
public class ThreadDemo4 {
public static void main(String[] args) {
MyThread2 myThread2=new MyThread2();
Thread thread=new Thread(myThread2);
thread.start();
}
}
class MyThread2 implements Runnable{
@Override
public void run(){
Thread thread=Thread.currentThread();
System.out.println("线程执行:"+thread.getName());
}
}
2.方式二:匿名内部类(Runnable)方式
public class ThreadDemo5 {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable(){
@Override
public void run(){
Thread t=Thread.currentThread();
System.out.println("执行线程:"+t.getName());
}
});
thread.start();
}
}
3.方式三:使用Lambda表达式来创建Runnable
如果是JDK8以上的版本创建线程,推荐使用此方式。
public class ThreadDemo6 {
public static void main(String[] args) {
Thread thread=new Thread(()->{
Thread t=Thread.currentThread();
System.out.println("任务执行:"+t.getName());
});
thread.start();
}
}
4.方式四:匿名方式创建子对象
public class ThreadDemo7 {
public static void main(String[] args) {
Thread thread=new Thread(){
@Override
public void run(){
Thread t=Thread.currentThread();
System.out.println("任务执行:"+t.getName());
}
};
thread.start();
}
}
注意:以上四种变种支持JDK1.8+版本。 上面创建线程的方式存在一个共同的问题就是没有返回值,也就是当线程执行完成之后,主线程没有办法拿到新线程的执行结果的。
1.3.3带返回值的Callable(2种)
方式一:实现Callable接口
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo8 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable=new MyCallable();
FutureTask<Integer> futureTask=new FutureTask<>(myCallable);
Thread thread =new Thread(futureTask);
thread.start();
int result=futureTask.get();
System.out.println(Thread.currentThread().getName()+"--新线程返回的结果为:"+result);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int randomNum=new Random().nextInt(10);
System.out.println(Thread.currentThread().getName()+"--随机数"+randomNum);
return randomNum;
}
}
方式二:匿名Callable
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo9 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask=new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
String[] arrs=new String[]{"java","MySQL","Thread"};
String result=arrs[new Random().nextInt(3)];
System.out.println(Thread.currentThread().getName()+"--字符传:"+result);
return result;
}
});
Thread thread=new Thread(futureTask);
thread.start();
String result=futureTask.get();
System.out.println(Thread.currentThread().getName()+" 新线程的返回值"+result);
}
}
1.4小结
创建线程有3大类实现方式,7种实现方法,如果是JDK1.8以上版本,在不需要获得线程执行结果情况下,推荐使用Lambda方式来创建线程,因为它的写法足够简洁;如果想要获取线程执行结果,可使用FutureTask+Callable的方式来实现。
2.线程常见构造方法
具体使用:
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
1.创建线程对象并命名:
public class ThreadDemo10 {
public static void main(String[] args) {
Thread t=new Thread("线程1"){
@Override
public void run(){
try {
Thread.sleep(60*60*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
}
}
2.使用Runnable对象创建线程并命名
public class ThreadDemo11 {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable(){
@Override
public void run(){
try {
Thread.sleep(60*60*100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Runnable-Thread");
thread.start();
}
}
3.线程分组使用
import java.util.Random;
public class ThreadDemo12 {
public static void main(String[] args) {
ThreadGroup group=new ThreadGroup("thread-group");
Runnable runTask= new Runnable() {
@Override
public void run(){
int num=(1+new Random().nextInt(3));
try {
Thread.sleep(num*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t=Thread.currentThread();
System.out.println(t.getName()+"--选手到达终点:"+num+"s");
}
};
Thread t1=new Thread(group,runTask);
Thread t2=new Thread(group,runTask);
Thread t3=new Thread(group,runTask);
t1.start();
t2.start();
t3.start();
while(group.activeGroupCount() == 0){
}
System.out.println("宣布比赛成绩");
}
}
线程执行顺序?
3.线程常用属性
(1)ID是线程的唯一标识,不同线程不会重复 (2)名称是各种调试工具用到 (3)状态表示线程当前所处的一个情况 (4)优先级高的线程理论上说更容易被调度到 (5)关于守护线程(后台线程):JVM会在一个进程的所有非后台线程结束后,才会结束执行。 (6)是否存活,即简单的理解,为run方法是否允许结束了。
3.1 ID和Name
public class ThreadDemo13 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new Runnable(){
@Override
public void run() {
Thread t=Thread.currentThread();
System.out.println("线程ID:"+t.getId());
System.out.println("线程名称:"+t.getName());
}
};
Thread thread=new Thread(runnable,"线程1");
thread.start();
Thread.sleep(500);
System.out.println();
Thread thread2=new Thread(runnable,"线程2");
thread2.start();
}
}
1.线程ID: 每个线程的id一定是不同的,是动态分配的。 2.线程名称 线程名称是可以手动指定的,并且线程名称是可能会存在重复的情况。
3.2线程的状态
public class ThreadDemoByState {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
Thread thread=Thread.currentThread();
System.out.println("线程状态2:"+thread.getState());
}
});
System.out.println("线程状态1:"+t.getState());
t.start();
Thread.sleep(500);
System.out.println("线程状态3:"+t.getState());
}
}
3.3线程优先级
1.获取线程优先级:
public class ThreadDemoByPriority {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
Thread t=Thread.currentThread();
System.out.println("线程优先级1:"+t.getPriority());
}
});
System.out.println("线程优先级2:"+thread.getPriority());
thread.start();
System.out.println("线程优先级3:"+thread.getPriority());
}
}
thread.getPriority();
(1)获取线程优先级,线程创建之后优先级就存在了。 (2)优先级是int类型值,默认值是5。线程的优先级1-10,最小的优先级是1,最高的优先级是10,默认的优先级是10.
2.设置线程优先级:
public class ThreadDemoByPriority2 {
private final static int MAXCOUNT=1000;
public static void main(String[] args) {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
Thread t=Thread.currentThread();
int priority=t.getPriority();
for (int i = 0; i <MAXCOUNT ; i++) {
System.out.println(t.getName()+"--优先级:"+priority);
}
}
},"线程1");
t1.setPriority(Thread.MAX_PRIORITY);
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
Thread t=Thread.currentThread();
int priority=t.getPriority();
for (int i = 0; i <MAXCOUNT ; i++) {
System.out.println(t.getName()+"--优先级:"+priority);
}
}
},"线程2");
t2.setPriority(Thread.MIN_PRIORITY);
Thread t3=new Thread(new Runnable() {
@Override
public void run() {
Thread t=Thread.currentThread();
int priority=t.getPriority();
for (int i = 0; i <MAXCOUNT ; i++) {
System.out.println(t.getName()+"--优先级:"+priority);
}
}
},"线程3");
t3.setPriority(Thread.NORM_PRIORITY);
t2.start();
t1.start();
t3.start();
}
}
注意事项: 同时启动多个线程,多个线程设置了不同的优先级,并不是优先级最高的就一定先执行完之后再执行低优先级的线程,而是高优先级的线程获取到CPU时间片的概率更多,整个的执行大致符合高优先级的线程最先执行完成。
3.4线程分类
用户线程
用户线程(main线程是默认用户线程)。
守护线程(后台线程)
1.守护线程是为用户线程服务的,当一个程序中的所有用户线程都结束之后,那么守护线程也会结束。 2.JVM中的垃圾回收器就是典型的守护线程,程序运行的时候它也运行,当满足条件是进行垃圾回收,当所有线程执行完任务终止时它也会随着退出。
守护线程使用
1.获取当前线程是否为守护线程:
thread.isDaemon();
结果为true表示是守护线程,false表示是用户线程 2.观察下面代码:
public class ThreadDemoByDaemon {
public static void main(String[] args) {
Thread thread=Thread.currentThread();
System.out.println("是否是守护线程:"+thread.isDaemon());
Thread t1=new Thread(()->{
Thread cThread=Thread.currentThread();
System.out.println(cThread.getName()+"--是否是守护线程"+cThread.isDaemon());
},"子线程1");
t1.start();
}
}
public class ThreadDemoByDaemon {
public static void main(String[] args) throws InterruptedException {
Thread thread=Thread.currentThread();
System.out.println("是否是守护线程:"+thread.isDaemon());
Thread t1=new Thread(()->{
Thread cThread=Thread.currentThread();
System.out.println(cThread.getName()+"--是否是守护线程"+cThread.isDaemon());
Thread tt1=new Thread(()->{
Thread cThread2=Thread.currentThread();
System.out.println(cThread2.getName()+"--是否是守护线程"+cThread.isDaemon());
},"子线程的子线程");
tt1.start();
},"子线程1");
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
}
}
结论: (1)main线程(主线程)默认是非守护线程(用户线程) (2)在用户线程中创建的子类也是用户线程(默认情况下)。 (3)在守护线程中创建的子线程默认情况下也是守护线程。
设置守护线程
thread.setDaemon(true);
守护线程注意事项
1.线程的类型(用户线程或守护线程)不能在线程运行期间,也就是调用了start()方法之后设置,如果设置那么JVM会报错。
用户线程和守护线程的区别
1.用户线程 结论:JVM会等待所有的用户线程全部执行完成之后才退出。 2.用户线程
public class ThreadDemoByDaemon2 {
public static void main(String[] args) throws InterruptedException {
daemonThread();
}
private static void daemonThread() {
Thread thread=new Thread(()->{
for (int i = 0; i <10 ; i++) {
System.out.println("执行:"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true);
thread.start();
}
private static void userThread() {
Thread thread=new Thread(()->{
for (int i = 0; i <10 ; i++) {
System.out.println("执行:"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
结论 :JVM会等待所有的用户线程执行完之后,再退出,但JVM不会等待守护线程执行完再退出。 3.区别:
public class ThreadDemoByDaemon2 {
public static void main(String[] args) throws InterruptedException {
userThread();
daemonThread();
}
private static void daemonThread() {
Thread thread=new Thread(()->{
for (int i = 0; i <10 ; i++) {
System.out.println("守护线程执行:"+i);
try {
Thread.sleep(850);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.setDaemon(true);
thread.start();
}
private static void userThread() {
Thread thread=new Thread(()->{
for (int i = 0; i <10 ; i++) {
System.out.println("用户线程执行:"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
守护线程 VS用户线程: 用户线程在java程序中非常重要,JVM一定要等所有的用户线程执行完之后才能自然结束,而守护线程就不一样了,守护线程是为用户线程服务的,所以当所有的用户线程都执行完之后,不管守护线程是否在执行,JVM都会退出执行。
3.5是否存活
1.判断线程是否存活,返回值是布尔值:
thread.isAlive();
public class ThreadDemoByAlive {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
Thread thread=Thread.currentThread();
System.out.println("线程是否存活2:"+thread.isAlive());
});
System.out.println("线程是否存活1:"+t.isAlive());
t.start();
System.out.println("线程是否存活3:"+t.isAlive());
Thread.sleep(1000);
System.out.println("线程是否存活4:"+t.isAlive());
}
}
2.isAlive()方法使用场景:
public class ThreadDemoByAlive {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("1.线程执行完了。");
});
t.start();
while(t.isAlive()){
}
System.out.println("2.确定线程执行完了!");
}
}
|