1、 java中的多线程
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
三、创建线程(1)定义一个类继承Thread类。
(2)覆盖Thread类中的run方法。
(3)直接创建Thread的子类对象创建线程。
(4)调用start方法开启线程并调用线程的任务run方法执行。
其中需要了解的是: start方法与run方法的区别?public class Test {
public static void main(String[] args) {
MyThread d1 = new MyThread("线程0");
MyThread d2 = new MyThread("线程1");
d1.start();// 开启线程,调用run方法。
d2.start();
System.out.println("结束:" + Thread.currentThread().getName());
}
}
class MyThread extends Thread {
private String name;
MyThread(String name) {
this.name = name;
}
public void run() {
for (int x = 0; x < 1111; x++) {
System.out.println(name + "....x=" + x + ".....name=" + getName());
}
}
}
其中Thread的getName()获取到线程的名称:Thread-编号(从0开始);
2、实现Runnable接口
步骤:
(1)定义类实现Runnable接口。
(2)覆盖接口中的run方法,将线程的任务代码封装到run方法中。
(3)通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
(4)调用线程对象的start方法开启线程。
public class Test {
public static void main(String[] args) {
MyThread d = new MyThread();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
class MyThread implements Runnable {
public void run() {
for (int x = 0; x < 1111; x++) {
System.out.println(Thread.currentThread().getName() + "....." + x);
}
}
}
实现Runnable接口的好处:(1)避免了java单继承的局限性
四、线程安全问题
线程安全问题产生的原因:
(1)多个线程在操作共享的数据。
(2)操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
解决思路:将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。而java中,同步代码块可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
//需要被同步的代码 ;
}
例子:
public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();// 创建一个线程任务对象。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable {
private int num = 100;
Object obj = new Object();
public void run() {
while (true) {
synchronized (obj) {
if (num > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"----"
+ num--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
同步的前提:同步中必须有多个线程并使用同一个锁。
五、同步的两种方式
1、同步代码块
2、同步函数:使用的锁是this,直接在函数前面加关键字synchronized
同步函数的锁是固定的this,同步代码块的锁是任意的对象,建议使用同步代码块。public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();// 创建一个线程任务对象。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
t.flag = false;
t2.start();
}
}
class Ticket implements Runnable {
private int num = 100;
boolean flag = true;
public void run() {
if (flag)
while (true) {
synchronized (this) {
if (num > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()
+ "...对象..." + num--);
} catch (InterruptedException e) {
}
}
}
}
else
while (true)
this.show();
}
public synchronized void show() {
if (num > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()
+ ".....函数...." + num--);
} catch (InterruptedException e) {
}
}
}
}
单例模式的线程同步:
普通的懒汉式:class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null)
s = new Single();
return s;
}
}
当单例模式使用在多线程的条件下:
class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) // 静态函数同步锁要这样写
{
if (s == null)
s = new Single();
}
}
return s;
}
}
其中:静态的同步函数使用的锁是:该函数所属字节码文件对象
可以用 getClass方法获取,也可以用当前类名.class 表示。
六、线程死锁
常见的死锁:同步的嵌套public class MyLock {
public static void main(String[] args) {
LockTest a = new LockTest(true);
LockTest b = new LockTest(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
class Lock {
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class LockTest implements Runnable {
private boolean flag;
LockTest(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
while (true)
synchronized (Lock.locka) {
System.out.println(Thread.currentThread().getName()
+ "..if locka....");
synchronized (Lock.lockb) {
System.out.println(Thread.currentThread().getName()
+ "..if lockb....");
}
}
} else {
while (true)
synchronized (Lock.lockb) {
System.out.println(Thread.currentThread().getName()
+ "..else lockb....");
synchronized (Lock.locka) {
System.out.println(Thread.currentThread().getName()
+ "..else locka....");
}
}
}
}
}
如上代码:当t1线程启动后,它获得了锁a,后又去申请获得b的锁,而此时a的锁没有放弃
当t2线程启动后,它获得了锁b,后又去申请获得a的锁,而此时b的锁没有放弃
两方都握有自己的锁不放弃,而同时申请另一方的锁,所以,此时就造成了死锁。
七:其他部分知识点:
wait和sleep的区别:
1、wait可以指定时间也可以不指定。
sleep必须指定时间。
2、在同步中时,对cpu的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。