线程
工作快一年了,对线程老是躲来躲去只要是线程出现的ANR就会避让,看来这种态度还是不行,如果在这样下去只会是一个普通,打工仔,花店时间写DOME吧
1 . Thread是个线程,而且有自己的生命周期
2 . 对于线程常用的操作有:wait(等待)、notify(唤醒)、notifyAll、sleep(睡眠)、join(阻塞)、yield(礼让)
3 . wait、notify、notifyAll都必须在synchronized中执行,否则会抛出异常
4 . synchronized关键字和ReentrantLock锁都是辅助线程同步使用的
5 . 初学者常犯的误区:一个对象只有一个锁(正确的)
线程同步synchronized(关键字)
比如抢火车票,在以前抢火车票是存在误差的,火车抢票是一年中沸沸扬扬的事情,这也就好比我们的多线程抢夺资源是一个道理,下面写一个例子:
public class ThreadT {
int number=10;
public void main(){
for (int i = 0; i < 10; i++) {
new Thread(){
public void run() {
addProdect();
};
}.start();
}
}
public void addProdect(){
number--;
System.out.println("商品数量"+(number));
}
}
这里我们开启了10个线程去抢火车票,不过火车票只有10张,需要一张一张来,比如先从10开始抢到最后一张为止。
输出结果:
商品数量6
商品数量6
商品数量6
商品数量6
商品数量5
商品数量2
商品数量2
商品数量2
商品数量1
商品数量0
很明显我们这个输出结果是错误的,可以看出来数据很乱,想要9,8,7,6,5,4,3,2,1,0,比如4个6就是因为有4个线程挤在一起4个线程一起执行了 number-- 接着在执行System.out.println("商品数量 = "+number);导致了4个6输出结果。
那么如果让他们自己排队就需要synchronized关键字。
一,方法上加上synchronized关键字
public class ThreadT {
int number=10;
public void main(){
for (int i = 0; i < 10; i++) {
new Thread(){
public void run() {
addProdect();
};
}.start();
}
}
//在这里添加synchronized关键字 进行线程同步
public synchronized void addProdect(){
number--;
System.out.println("商品数量"+(number));
}
}
这里就是表示方法同步,让线程一个一个来抢夺资源,下面是执行结果
商品数量9
商品数量8
商品数量7
商品数量6
商品数量5
商品数量4
商品数量3
商品数量2
商品数量1
商品数量0
二,方法内部添加synchroized关键字
public class ThreadT {
int number=10;
public void main(){
for (int i = 0; i < 10; i++) {
new Thread(){
public void run() {
addProdect();
};
}.start();
}
}
//在这里添加synchronized关键字 进行线程同步
Object obj = new Object();
public void addProdect(){
synchronized (obj) {
number--;
System.out.println("商品数量"+(number));
}
}
}
其实synchronized可以理解为一个锁,而锁就是为了锁东西,所以synchronized又分为类锁和对象锁 ,即可以锁类也可锁对象,他们两者的作用就是为了保证线程同步。就好比上面的synchronized(obj){},就是对象锁,将Object对象锁起来。
类锁与对象锁的概念
对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是在多线程访问时,两个锁实际是很大区别的,对象锁是用于对象实例方法,或者对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知到,类的对象实例可以有很多给,但是每个类只有一个class对象,所以 结论是 : 1,不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。而且类锁和对象锁互相不干扰
一,对象锁
对象锁创建下面两个方法
public class ThreadLock {
//同步方法,对象锁
public synchronized void methodObject(){
}
//同步快,对象锁
public void syncThis(){
synchronized (this) {
}
}
}
二,类锁
类锁创建下面两个方法
public class ThreadLock {
//同步class对象,类锁
public void classClockObject(){
synchronized (ThreadLock.class) {
}
}
//同步静态方,法类锁
public static synchronized void syncThis(){
}
}
三,写个锁的例子
根据类锁和对象锁的概念,我们来通过例子验证一下其正确性,这里演示两个对象锁和一个类锁,我们创建一个类
public class ThreadLock {
int number = 10;
//同步方法,对象锁
public synchronized void syncMethodLock(){
for (int i = 0; i < 10; i++) {
number--;
System.out.println(Thread.currentThread().getName() + "剩余的数:" + number);
}
}
//同步块,对象锁
public void syncObjectLock(){
synchronized (this) {
for (int i = 0; i < 10; i++) {
number--;
System.out.println(Thread.currentThread().getName() + "剩余的数:" + number);
}
}
}
//同步class对象,类锁
public void syncClassLock(){
synchronized (ThreadLock.class) {
for (int i = 0; i < 50; i++) {
number--;
System.out.println(Thread.currentThread().getName() + "剩余的数:" + number);
}
}
}
}
public class TestMain {
public static void main(String[] args) {
// ThreadT t = new ThreadT();
// t.main();
final ThreadLock lock = new ThreadLock();
new Thread(){
public void run() {
lock.syncMethodLock();
};
}.start();
new Thread(){
public void run() {
lock.syncObjectLock();
};
}.start();
}
}
由于使用的是同一个对象的对象锁,所以执行出来的结果是同步的(即先运行线程一,等线程一运行完后运行线程二,ticket有序的减少)
输出结果
Thread-0剩余的数:9
Thread-0剩余的数:8
Thread-0剩余的数:7
Thread-0剩余的数:6
Thread-0剩余的数:5
Thread-0剩余的数:4
Thread-0剩余的数:3
Thread-0剩余的数:2
Thread-0剩余的数:1
Thread-0剩余的数:0
Thread-1剩余的数:-1
Thread-1剩余的数:-2
Thread-1剩余的数:-3
Thread-1剩余的数:-4
Thread-1剩余的数:-5
Thread-1剩余的数:-6
Thread-1剩余的数:-7
Thread-1剩余的数:-8
Thread-1剩余的数:-9
Thread-1剩余的数:-10
由于对象锁和类锁互不干扰,所以也是线程不安全的
如果不加 同步方法,对象锁 和 同步块,对象锁 会导致输出异常
异常结果:
Thread-0剩余的数:9
Thread-0剩余的数:8
Thread-0剩余的数:7
Thread-0剩余的数:6
Thread-0剩余的数:4
Thread-1剩余的数:4
Thread-1剩余的数:2
Thread-1剩余的数:1
Thread-1剩余的数:0
Thread-1剩余的数:-1
Thread-1剩余的数:-2
Thread-1剩余的数:-3
Thread-1剩余的数:-4
Thread-1剩余的数:-5
Thread-1剩余的数:-6
Thread-0剩余的数:3
Thread-0剩余的数:-7
Thread-0剩余的数:-8
Thread-0剩余的数:-9
Thread-0剩余的数:-10
2、不同对象,使用两个线程调用同个对象锁
public class TestMain {
public static void main(String[] args) {
final ThreadLock lock1 = new ThreadLock();
final ThreadLock lock2 = new ThreadLock();
//线程一
new Thread(){
public void run() {
lock1.syncMethodLock();
};
}.start();
//线程二
new Thread(){
public void run() {
lock2.syncMethodLock();
};
}.start();
}
}
输出数据
Thread-0剩余的数:9
Thread-0剩余的数:8
Thread-0剩余的数:7
Thread-0剩余的数:6
Thread-0剩余的数:5
Thread-0剩余的数:4
Thread-0剩余的数:3
Thread-0剩余的数:2
Thread-0剩余的数:1
Thread-1剩余的数:9
Thread-0剩余的数:0
Thread-1剩余的数:8
Thread-1剩余的数:7
Thread-1剩余的数:6
Thread-1剩余的数:5
Thread-1剩余的数:4
Thread-1剩余的数:3
Thread-1剩余的数:2
Thread-1剩余的数:1
Thread-1剩余的数:0
由于是不同对象,所以执行的对象锁都不是不同的,其结果是两个线程互相抢占资源的运行,即number偶尔会无序的减少
温习结论:1、不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。2、而且类锁和对象锁互相不干扰。
线程同步ReentrantLock锁
Java6.0增加了一种新的机制:ReentrantLock,下面看ReentrantLock的使用
import java.util.concurrent.locks.Lock;
public class ReentantLock {
Lock lock;
public void reentantLocks(){
lock = (Lock) new ReentantLock();
doStart();
}
public void doStart(){
lock.lock();
try{
//同步代码块
}finally{
lock.unlock();
}
}
}
使用ReentrantLock很好理解,就好比我们现实的锁头是一样道理的。使用ReentrantLock的一般组合是lock与unlock成对出现的,需要注意的是,千万不要忘记调用unlock来释放锁,否则可能会引发死锁等问题。如果忘记了在finally块中释放锁,可能会在程序中留下一个定时炸弹,随时都会炸了,而是用synchronized,JVM将确保锁会获得自动释放,这也是为什么Lock没有完全替代掉synchronized的原因
线程生命周期
线程也有属于自己的生命周期 下面画图来解释一下

线程的等待唤醒机制 wait()等待、notify()唤醒、notifyAll()
一开始我们也提到了wait、notify、notifyAll都必须在synchronized中执行,否则会抛出异常。所以下面以一个简单的例子来介绍线程的等待唤醒机制
//线程等待and唤醒
public class ThreadWaitNotify {
private static Object objLock = new Object();
public void main(){
System.out.println("主线程运行");
Thread threads = new Threads();
threads.start();
synchronized (objLock) {
long start = System.currentTimeMillis();
System.out.println("主线程等待......");
try {
objLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程 --> 等待的时间:" + (System.currentTimeMillis() - start));
}
}
class Threads extends Thread{
@Override
public void run() {
synchronized (objLock) {
try {
//子线程等待了2秒钟后唤醒objLock 锁
Thread.sleep(2000);
objLock.notifyAll();//唤醒线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
}
}
}
}
输出结果
主线程运行
主线程等待......
主线程 --> 等待的时间:2001
可以看到,我们使用的是同一个对象的锁,和同一个对象执行的wait()和notify()才会保证了我们的线程同步。当主线程执行到wait()方法时,代表主线程等待,让出使用权让子线程执行,这个时候主线程等待这一事件会被加进到【等待唤醒的队列】中。然后子线程则是两秒钟后执行notify()方法唤醒等待【唤醒队列中】的第一个线程,这里指的是主线程。而notifyAll()方法则是唤醒整个【唤醒队列中】的所有线程,这里就不多加演示了
写一道练习题目 :子子线程循环5次,接着主线程循环5次,接着又回到子线程循环5次,接着再回到主线程又循环19次,保证数据正确
public class Practice {
Object objLock = new Object();
public void main(){
//子线程循环5次,接着主线程循环5次,接着又回到子线程循环5次,接着再回到主线程又循环19次,保证数据正确
//子线程
new Thread() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
synchronized (objLock) {
for (int j = 0; j < 5; j++) {
System.out.println("子循环循环第" + (j + 1) + "次");
}
System.out.println("--> 子线程循环了" + (i + 1) + "次");
objLock.notify();
try {
objLock.wait(); //等待去执行主线程执行数据
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}.start();
//主线程
for (int i = 0; i < 5; i++) {
synchronized (objLock) {
try {
objLock.wait(); //先锁住,等待子线程唤醒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (int j = 0; j < 10; j++) {
System.out.println("主循环循环第" + (j + 1) + "次");
}
objLock.notify();
System.out.println("--> 主线程循环了" + (i + 1) + "次");
}
}
}
}
不管是主线程先运行还是子线程运行,两个线程只能同时进入synchronized (lock)一个锁中。由于是子线程先运行:1、当主线程先进入synchronized (lock)锁时,它就必须是等待,而子线程开始运行输出,输出后就唤醒主线程。2、当子线程先运行的话,那就直接输出,然后等待主线程的运行输出
线程的sleep()、join()、yield()
一、sleep()
sleep()作用是让线程休息指定的时间,时间一到就继续运行,它的使用很简单
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
看图

二、join()
join()【阻塞】作用是让指定的线程先执行完再执行其他线程,而且会阻塞主线程,它的使用也很简单
public class join {
public void main() throws InterruptedException{
Thread thread1 = new MyThread("线程一");
Thread thread2 = new MyThread("线程二");
thread1.start();
thread1.join();
System.out.println("主线程等待");
thread2.start();
thread2.join();
}
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
super.run();
try {
System.out.println(getName() + " -> 在运行....");
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
输出结果 :
线程一 -> 在运行....
主线程等待
线程二 -> 在运行....
看图:

三、yield()
yield()的作用是指定线程先礼让一下别的线程的先执行,就好比公交车只有一个座位,谁礼让了谁就坐上去。特别注意的是:yield()会礼让给相同优先级的或者是优先级更高的线程执行,不过yield()这个方法只是把线程的执行状态打回准备就绪状态,所以执行了该方法后,有可能马上又开始运行,有可能等待很长时间
public class join {
public void main() throws InterruptedException{
Thread thread1 = new MyThread("线程一");
Thread thread2 = new MyThread("线程二");
thread1.start();
thread2.start();
}
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public synchronized void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "在运行,i的值为:" + i + " 优先级为:" + getPriority());
if (i == 2) {
System.out.println(getName() + "礼让");
Thread.yield();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
运行结果
线程一在运行,i的值为:0 优先级为:5
线程一在运行,i的值为:1 优先级为:5
线程二在运行,i的值为:0 优先级为:5
线程二在运行,i的值为:1 优先级为:5
线程二在运行,i的值为:2 优先级为:5
线程一在运行,i的值为:2 优先级为:5
线程一礼让
线程二礼让
线程一在运行,i的值为:3 优先级为:5
线程一在运行,i的值为:4 优先级为:5
线程一在运行,i的值为:5 优先级为:5
线程一在运行,i的值为:6 优先级为:5
线程一在运行,i的值为:7 优先级为:5
线程一在运行,i的值为:8 优先级为:5
线程一在运行,i的值为:9 优先级为:5
线程二在运行,i的值为:3 优先级为:5
线程二在运行,i的值为:4 优先级为:5
线程二在运行,i的值为:5 优先级为:5
线程二在运行,i的值为:6 优先级为:5
线程二在运行,i的值为:7 优先级为:5
线程二在运行,i的值为:8 优先级为:5
线程二在运行,i的值为:9 优先级为:5
结束语:
这篇文章写了差不多一天了,简介其他文章写出的,加上demo,可能过几天没用又会忘记,但是写了文章加图一看应该可以大致回忆过来。最近出现一个wait异常at java.lang.Object.wait(Native method)才来了解线程这些特效,内容有点多当前我是对线程收悉一点了,不知道各位看官怎么样了。如有错误位置其提出,做修改谢谢。

本文深入探讨线程同步机制,包括synchronized关键字与ReentrantLock的使用,解析线程的生命周期及wait、notify、sleep、join、yield等操作的作用。通过实例演示,帮助读者理解线程同步的重要性和实现方式。
745

被折叠的 条评论
为什么被折叠?



