(1)线程同步:在单线程程序中,每次只能做一件事情。后面的事情需要等待前面的事情完成后才可以进行,但是如果使用多线程程序,就会发生两个或多个线程抢占资源的问题,如两个人同时说话,两个人同时过同一个独木桥。所以在多线程编程中需要防止这些资源访问的冲突。Java提供了线程同步机制来防止资源访问的冲突。
(2)线程安全:实际开发中,使用多线程程序的情况会很多,如车站售票系统,医院挂号系统。这种多线程的程序通常会发生问题,以车站售票系统为例,当代码中判断当前票数是否大于0,如果大于0则执行将该票出售给乘客的功能,但当两个线程同时访问这段代码时(假如这时只剩下一张票),第一个线程将票售出,与此同时第二个线程也已经执行完成判断是否有票的操作,并得出票数大于0的结论,于是他也执行售出操作,这样就会产出负数。所以,在编写多线程程序时,应该考虑到线程安全问题。实质上线程安全问题来源于两个线程同时存取单一对象的数据。
(3)★安全问题出现的条件:☆是多线程环境 ☆有共享数据 ☆有多条语句操作共享数据
★如何解决多线程安全问题:基本思想就是让程序没有安全问题的环境。采用线程同步机制,把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
(4)同步的好处与弊端
☆好处:解决了多线程数据安全问题。
☆弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
2.0 卖票---暴露出安全问题
public class Demo01 implements Runnable{
private static int tickets =100;
private Object o =new Object();
@Override
public void run() {
while (true) {
if (tickets <= 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + ":" + tickets + "张票");
}
}
}
}
public class Test01 {
public static void main(String[] args) {
Demo01 d =new Demo01();
Thread t1 =new Thread(d,"窗口1");
Thread t2 =new Thread(d,"窗口2");
Thread t3 =new Thread(d,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
出现的问题:有相同的票出现。同时也出现了负数票。
问题产生的原因:发生了线程安全问题,是线程执行的随机性导致的,可能在买票过程中丢失cpu的执行权,导致出现问题。
synchronized(任意对象){
多条语句操作共享数据的代码
}
public class Demo01 implements Runnable{
private static int tickets =100;
private Object o =new Object();
@Override
public void run() {
while (true) {
synchronized (o) {
if (tickets <= 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + ":" + tickets + "张票");
}
}
}
}
}
public class Test01 {
public static void main(String[] args) {
Demo01 d =new Demo01();
Thread t1 =new Thread(d,"窗口1");
Thread t2 =new Thread(d,"窗口2");
Thread t3 =new Thread(d,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
分析一些问题
public class Demo01 implements Runnable{
private static int tickets =100;
@Override
public void run() {
while (true) {
synchronized ( new Object()) {
if (tickets <= 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() + ":" + tickets + "张票");
}
}
}
}
}
这样也会出现,打印出的结果有重复的,出现负数,原因是每次循环结束的时候synchronized都会换新的对象,这样就相当于换新的锁,根本监视不到对象,所以把object o放在了成员变量,这样就一直是一把锁,利用这种原理可以试出来后面的同步方法的对象是this和静态同步方法的对象是 类名.class
同步方法:就是把synchronized关键字加到方法上。
修饰符 synchronized 返回值类型 方法名(方法参数){
方法体
}
同步静态方法:在同步方法的基础上加上static关键字
修饰符 static synchronized 返回值类型 方法名(方法参数){
方法体
}
同步方法的锁对象:this
同步静态方法的锁对象:类名.class
该代码用来解释为什么同步方法的锁对象是this,同理证明同步静态方法的锁对象。
public class Demo02 implements Runnable{
private static int ticket =100;
private Object o =new Object();
private int x=0;
@Override
public void run() {
l: while (true) {
if(x%2==0) {
synchronized (o) { 用o的时候,发现会有安全问题把o改成this 发现没有问题
if (ticket <= 0) { 证明了同步方法的锁对象是this
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
}
}
}
else{
if (ticket <= 0) {
break ;
}
method();
}
x++;
}
}
private synchronized void method(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
}
}
同步方法的综合举例
public class Demo03 implements Runnable{
private static int ticket=100;
@Override
public void run() {
while(true){
if("窗口一".equals(Thread.currentThread().getName())){
boolean result = Method();
if(result){
break;
}
}
if("窗口二".equals(Thread.currentThread().getName())){
synchronized (Demo03.class){
if(ticket<=0){
break;
}
else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}
}
}
}
}
private static synchronized boolean Method() {
if (ticket <= 0) {
return true;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
return false;
}
}
}
public class Test03 {
public static void main(String[] args) {
Demo03 d =new Demo03();
Thread t1 =new Thread(d,"窗口一");
Thread t2 =new Thread(d,"窗口二");
t1.start();
t2.start();
}
}
(1)介绍:虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达在哪里加上了锁,在哪里释放了锁,JDK5以后提供了一个新的锁对象Lock
(2)Lock是一个接口(Public interface Lock)不能直接实例化,我们这里是采取它的实现类ReentrantLock来实例化的。
(3)Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
(4)Lock提供的方法
Void lock() 获得锁
Void unLock( ) 释放锁
public class Demo04 implements Runnable{
private int ticket=100;
Lock lock =new ReentrantLock();//多态的形式。
@Override
public void run() {
while(true) {
try{ lock.lock();
if(ticket <= 0) {
break;
}
else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
} }
finally {
lock.unlock();}
}
}
}
public class Test04 {
public static void main(String[] args) {
Demo04 d =new Demo04();
Thread t1 =new Thread(d,"窗口1");
Thread t2 =new Thread(d,"窗口2");
t1.start();
t2.start();
}
}
我们不用下面这种方式写lock的代码,一般采用上面的try finally代码块的形式写,虽然下面的也能运行,但会在一定的条件下会出错,导致无法释放锁,以及一系列问题。
public class Demo04 implements Runnable{
private int ticket=100;
Lock lock =new ReentrantLock();//多态的形式。
@Override
public void run() {
while(true) {
lock.lock();
if(ticket <= 0) {
break;
}
else{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}
lock.unlock();
}
}
}
概念:编程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
public class Test0 {
public static void main(String[] args) {
Object a =new Object();
Object b =new Object();
new Thread(()->{
synchronized (a){
System.out.println("i get a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i want b");
synchronized (b){
System.out.println("i get b");
}
}
}).start();
new Thread(()->{
synchronized (b){
System.out.println("i get b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i want a");
synchronized (a){
System.out.println("i get a");
}
}
}).start();
}
}
运行结果
i get a
i get b
i want a
i want b
程序就僵在这了
由于第一个线程,占用了a锁想要b锁,而第二个线程占用了b锁想要a锁。故僵在这了,形成死锁。