一、线程的基本问题
操作系统中可以独立运行的程序叫进程,进程有多个执行单元,这些执行单元可以叫做线程。
一个进程至少包括一个线程,线程也叫轻量级进程。
同操作系统调用进程一样,进程里的多个线程看似同时执行。其实也是由CPU轮流执行的,只不过CPU运行速度很快,故而给人同时运行的错觉。
建立线程有两种方式
1.继承Thread类
class MyThread extends Thread{
public void run(){……};//一定要写的方法,必须这么写
//必须这么写的原因有三条
//1.如果改变public为protected等,然而父类中的run方法还是public。规则是子类不能比父类的级别低,更改的话就违反了这条
//2.如果添加参数,则变为普通方法,不会在调用start()方法后执行run()方法。
//3.只认void的返回值,否则编译时就会报错
}
创建线程对象时则使用 MyThread my = new MyThread();
my.start();//start()方法用于启动新线程,系统会自动调用run()方法。
若要为进程重命名则可以重写MyThread的构造方法,比如
public MyThread(String string) {
this.setName(string);
}
调用时使用new MyThread("name1")就行了。
2.实现Runnable接口
class MyThread implements Runnable{
public void run(){……};//Runnable中只有这一个方法,所以一定要实现
}
创建线程对象时,需要调用Thread()的构造函数才行
MyThread my = new MyThread();
Thread td = new Thread(my);
td.start();
简化版1
Thread td = new Thread(new MyThread());
td.start()
;
简化版2
new Thread(new MyTherad()).start();
Thread的构造方法中还可以传入线程名比如new Thread(my,“线程名”)
两种方法的对比。
Runnable 更适合多个线程处理一个资源的情况,把线程同程序代码、数据有效的分离,很好的体现了面向对象的设计思想。
Thread创建的不同线程拥有不同的资源。
Runnable 可以避免Java的单继承带来的局限性。
获取当前线程的名字可以通过Thread.currentThread().getName();
二、线程的同步问题
实现同步问题有两个方法,同步代码块和同步方法,关键字是synchronized
容易知道,同步代码块的锁是用户自定义的任意对象。
那么同步方法当然也有锁,就是调用该方法的对象,也就是this指向的对象,可以用this获取。因为对象对线程来说也具有唯一性,所以可以作为锁。
如果同步的是静态方法,不需要创建对象就可以调用,那么静态方法的锁就是该方法所在类的class对象,可以用“类名.class”获取。
同步解决了多个线程同时访问共享数据的线程安全问题,即同一时间只能由一个线程访问公共资源。
但是因为每次都要判断锁的状态,所以非常消耗资源,效率低。
当两个线程都需要使用对方的锁,但又不释放自己的锁就会造成死锁。
java虚拟机没有检测,也没有采用措施来处理死锁情况,所以多线程编程是应该采取措施避免死锁的出现。
一旦出现死锁,整个程序即不会发生任何异常,也不会给出任何提示,只是所有线程都处于堵塞状态。
三、其他注意的
1.解决线程之间的通信问题的方法wait()、notify()、notifyAll()。
2.sleep()和wait()方法的区别?
sleep()方法让出CPU但不会释放同步锁,等时间到了之后再执行当前线程
wait()在同步锁内调用,释放同步锁,被唤醒后进入就绪状态等待下一次获得同步锁
3.yield()和join()
yield()让出控制权,进入就绪状态使系统重新调用一次,不会阻塞进程。某个进程yield()之后,只有优先级>=当前进程优先级的才有可能被调用
当A线程中使用B.join()时,A将被阻塞,等B执行完时A才能继续执行。
4.不同操作系统对线程优先级的支持不一样,不能很好的和Java中的线程优先级一一对应,所以不能依赖优先级。
5.对Java来说,只要还有一个前台线程在运行,这个进程就不会结束,但只有后台线程在运行,这个进程就会结束。
可以使用 线程.setDaemon(true),来使前台线程转换成后台线程,而且必须在start()方法之前使用,否则报错。
前后台线程是相对的概念,新创建的线程默认都是前台线程。
6.线程的5种状态和转换
.