第22章 java线程(2)-线程同步

java线程(2)-线程同步

本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式

1.线程不安全问题

什么叫线程不安全呢

即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题

对于前一章例子中,使用接口实现方式时会有重复现象,使用接口方式时我们还没有发现明显的现象,但是这并不代表原来的代码没有问题

我们发现没有问题,必须要有这个意识:看不到问题,有可能是我们经验太少,或者问题出现的不够明显。

如果问题不够明显,我们可以使用Thread.sleep()方法,正在运行的线程暂停,此时会执行另外的进程,如此可以更方便的看到问题

注意:

在线程的run方法上不能使用throws来声明抛出异常,只能在方法中使用try-catch来处理异常

原因是:子类覆盖父类的原则,子类不能抛出新的异常。

在Runnable接口中的run方法,都没有抛出异常,所以子类中也不能对这个方法抛出异常

父类中:public abstract void run();

解决方法的原理

回到我们用继承方法的问题上来,如何才能让一个线程没有执行完时另一个线程不会操作一些动作呢。

解决方案是:保证某一些的动作的同步完成。即保证打印苹果和苹果总数-1操作,必须同步,作为一个单元来执行

具体的解决方法有3个:

方式1:同步代码块

方式2:同步方法

方式3:锁机制(lock)

下面分别实现三种方法

2.线程同步

思想是把不能中断或者分开执行的代码绑定在一起执行,使其不能在执行中中断

2.1.同步代码块

语法:

synchronized(同步锁)
{
需要同步操作的代码
}

同步锁:

为了保证没一个线程都能正常执行原子操作,java引入了线程同步机制

同步锁又被称为:同步监听对象/同步监听器/互斥锁

其实就是相当与一把只有一个坑位的卫生间门上的锁,当里面有人的时候,门必须要被锁上,等人出来了再把锁打开

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁

java程序运行使用任何对象作为同步锁,但是一般的,我们是用当前并发访问的共同资源作为同步监听对象

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程,只能在外面等着

示例代码:

//接口实现方式
class Apple1 implements Runnable{
private int num = 50; public void run(){
for (int i = 0; i < 50; i++) {
synchronized (this){
if (num > 0){
System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}
} }
}
} public class appleImplements {
public static void main(String[] args) {
//创建桑个线程
Apple1 a = new Apple1();
new Thread(a,"小A").start();
new Thread(a,"小B").start();
new Thread(a,"小C").start();
}
}

注意:因为继承方法没有不能实现资源的共享,所以这里的例子都是以接口方式实现的。

2.2.同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法。保证A线程执行该方法的时候,其他线程只能在外面等着

语法:

synchronized public void doWork(){
//TODO 要执行的动作
}

同步锁是谁:

对于非static方法,同步锁就是this

对于static方法,我们使用当前方法所在的类的字节码对象(Apple2.class)

注意:不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能,好比是多个线程出现串行

代码实现:


//接口实现方式
class Apple2 implements Runnable{
private int num = 500; public void run(){
for (int i = 0; i < 500; i++) {
eat(); }
} synchronized private void eat(){
if (num > 0){
System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
try {
Thread.sleep(10);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}
}
} public class appleImplements {
public static void main(String[] args) {
//创建桑个线程
Apple2 a = new Apple2();
new Thread(a,"小A").start();
new Thread(a,"小B").start();
new Thread(a,"小C").start();
}
}

2.3.同步锁(Lock)

Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象

语法:

第22章  java线程(2)-线程同步

思想就是,在执行要绑定的代码之前,上一把锁,执行完之后呢,就把锁打开,这样别的线程就能继续了。

在使用这个锁之前呢,要先new一个锁出来

代码示例;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; //接口实现方式
class Apple4 implements Runnable{
private int num = 50;
private final Lock lock = new ReentrantLock();//创建一个锁对象
public void run(){
for (int i = 0; i < 50; i++) {
eat(); }
} private void eat(){
//进入方法:立马加锁
lock.lock();//获取锁
if (num > 0){
try {
System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
Thread.sleep(100);//模拟网络延迟
num--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//执行完,走人,并把锁打开
lock.unlock();//释放锁
}
}
}
} public class appleImplements {
public static void main(String[] args) {
//创建桑个线程
Apple4 a = new Apple4();
new Thread(a,"小A").start();
new Thread(a,"小B").start();
new Thread(a,"小C").start();
}
}

2.4.synchronized的好与坏

好处:保证了多线程并并发访问时的同步操作,避免线程的安全性问题

缺点:使用synchronized方法/代码块的性能比不用要低一些

建议:尽量减小synchronized的作用域

上一篇:使用js冒泡实现点击空白处关闭弹窗


下一篇:mysql函数之六:mysql插入数据后返回自增ID的方法,last_insert_id(),selectkey