package day9.ten;
/*
*1.问题:卖票过程中,出现了冲票,错票-->出现了线程的安全问题
* 2.问题出现的原因:当莫格线程操作车票的过程中,尚未操作完成时,其他线程参与进来
* 3.如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来,直到线程a操作完,其他线程才可以操作ticket。
* 即使线程a出现了组设,也不能改变
*/
class Window1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if (ticket > 0) {
//增加阻塞时间,阻塞时间越长,在这个范围内,出现的错误越多
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
结果显示,有好多重复的票数,造成了多卖现象,多运行几次,票数还可能是负数
实现Runnable接口
package day9.ten;
/*
* 方式一:同步代码块
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:1.操作共享数据的代码,即为需要被同步的代码, -->不能包含多了(可能只剩下一个线程做事),也不能包含代码少了,最好只包含共享数据
* 2.共享数据:多个线程共同操作的变量。比如:ticket
* 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
* 要求:多个线程必须要公用统一把锁。
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
*/
class Window1 implements Runnable{
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
// obj可以是任何类的对象,包括你自己创的对象,当作锁,当一个线程操作共享资源时,就锁柱,只允许这个线程操作,只有这个线程操作好,其他线程才能操作
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
结果:线程安全
同步监视器:可以是任何对象,当你不想new对象是,也可以写入this,当前对象来充当同步监视器。
继承Thread实现线程安全
class Window extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while(true){
// synchronized (obj) {
synchronized (obj){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(getName() + ":票号:" + ticket);
ticket --;
} else {
break;
}
}
}
}
}
public class WindowTest{
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
二者原理相同,不过这个需要把同步监视器需要的对象设置为静态的,要不然对象就不唯一了。
同样这个不需要创建对象时,可以使用Window.class,把当前类当作对象充当同步监视器
实现Runnable接口的线程
package day9.ten;
/*
*使用同步方法解决实现Runnable接口的线程安全问题
* 把操作贡献资源的代码抽离出来,作为同步方法
*/
class Window3 implements Runnable{
private int ticket = 100;
@Override
public void run() {
// 默认同步监视器:this
while(true){
show();
}
}
// 把操作贡献资源的代码抽离出来,作为同步方法
private synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
继承Thread类的线程
package day9.ten;
/*
*使用同步方法解决继承Thread类的线程安全问题
*/
class Window4 extends Thread{
private static int ticket = 100;
@Override
public void run() {
while(true){
show();
}
}
public static synchronized void show(){
// 同步监视器。需要设为静态方法
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":票号:" + ticket);
ticket --;
}
}
}
public class WindowTest4{
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
关于同步方法的总结:
package day9.tenone;
/*
*解决线程安全问题的方式三:Lock锁 ---- JDK5.0新增
*
* 1.面试题:synchronized与Lock的异同?
* 相同:二者都可以解决线程安全问题
* 不同:synchronized机制在执行完响应的同步代码以后,自动的释放同步监视器
* lock需要手动的启动同步(Lock()),同时结束同步需要手动的实现(unlock())
*/
import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable{
public int ticket = 100;
// 1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while(true){
try {
// 2.调用lock()
lock.lock();
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为" + ticket);
ticket --;
}else {
break;
}
}finally {
//3.调用解锁方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w1 = new Window();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
优先使用顺序:
Lock →同步代码块(已经进入了方法体,分配了相应资源)→同步方法(在方法体之外)
实现Runnable接口的类,充当参数传进去Thread的构造器,不需要把变量,方法设为静态,就可以充当共享资源。而继承Thread类的线程,需要把变量,方法设置为静态的才能充当共享资源。
在线程安全中,主要是每次操作共享资源时,把这个操作锁住,防止其他线程操作,才能实现线程安全。锁是通过对象充当的,用对象当作锁去锁住操作,防止其他线程操作,当用锁锁住了这个操作,这个锁就在这里了,别人不能用。
同步的方式,解决了线程的安全问题。—好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的的过程,效率低。
死锁例子
/*
*演示线程的死锁问题
* 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
* 都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
*
* 2.说明:
* 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
* 2)我们使用同步时,避免出现死锁
*
* 锁被一个线程使用之后,只能等待这个线程操作完,才能释放锁,期间别人拿不到。如果互相需要对面的锁,就形成了死锁,谁也得不到对方的,但是又需要对面的锁才能执行下一步
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
// 模拟阻塞状态
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程