1.基本的线程同步的方法
a.线程安全问题存在的原因?
由于一个线程在操作共享数据的过程中,未执行完毕,另外的线程参与进来,导致共享数据出现安全问题。
b.如何解决线程安全问题?
必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作。
c.Java如何实现线程的安全
使用线程的同步机制。
方式一:同步代码块
Synchronized(同步监视器){
//需要被同步的代码块(即操作共享数据的代码)
}
a.共享数据:多个线程共同操作一个数据(变量)
b.同步监视器:由任何一个类的对象来充当,哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称锁。【这种方式很像厕所上面的锁,一个人进去,锁上门,其他人就进不去】
要求:所有的线程必须共用同一把锁!在实现Runnable接口的方法中,可以用this作为同步锁。但继承Thread的方法中,慎用this作为同步锁。
实现Runnable接口的方法
package TestThread;
public class ImplementSecretThread implements Runnable {
int ticket=100;
@Override
public void run() {
while(true){
try {
Thread.currentThread().sleep(3); //这可以令线程挂起,放大线程安全问题
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this){ //使用当前对象作为同步锁
if(ticket>0)
System.out.println(Thread.currentThread().getName()+"卖票,票号:"+ticket--);
else
break;
}
}
}
}
@Test
public void testSecretThread(){
System.out.println("---------------start--------------------");
ImplementSecretThread p = new ImplementSecretThread();
Thread w1=new Thread(p);
Thread w2=new Thread(p);
Thread w3=new Thread(p);
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
try {
w1.join();
w2.join();
w3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---------------end--------------------");
}
继承Thread的方法
package TestThread;
public class GeneticSecretThread extends Thread{
static int ticket=100; //这种方法生命周期长,是整个程序的生命周期
static Object obj=new Object();
public void run(){
while(true){
try {
Thread.currentThread().sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj){
if(ticket>0)
System.out.println(Thread.currentThread().getName()+"卖票,票号:"+ticket--);
else
break;
}
}
}
}
@Test
public void testGeneticSecretThread(){
System.out.println("---------------start--------------------");
GeneticSecretThread w1=new GeneticSecretThread();
GeneticSecretThread w2=new GeneticSecretThread();
GeneticSecretThread w3=new GeneticSecretThread();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
try {
w1.join();
w2.join();
w3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---------------end--------------------");
}
方式二:同步方法
将操作共享数据的方法声明为synchronized,即方法为同步方法,这能够保证一个线程执行此方法时,其他线程在外等待直到此线程执行完此方法。
同步方法的锁默认为this。
2.释放锁的情况
释放锁的方法有:
wait();
不释放锁的方法有:
Thread.sleep();
Thread.yield();
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程
释放锁的其他操作:
l 当前线程的同步方法、同步代码块执行结束
l 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
l 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
l 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
3.一个线程死锁的例子
class A {
public synchronized void foo(B b) { //A的锁
System.out.println(Thread.currentThread().getName()
+ " 进入了A实例的foo方法,获得A的锁");
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 企图调用B实例的last方法,需要B的锁");
b.last();
}
public synchronized void last() { //A的锁
System.out.println("进入了A类的last方法内部");
}
}
class B {
public synchronized void bar(A a) { //B的锁
System.out.println(Thread.currentThread().getName()
+ " 进入了B实例的bar方法,获得B的锁");
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 企图调用A实例的last方法,需要A的锁");
a.last();
}
public synchronized void last() { //B的锁
System.out.println("进入了B类的last方法内部");
}
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主线程");
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
DeadLock dl = new DeadLock();
new Thread(dl).start();
dl.init();
}
}
死锁的问题,处理线程同步时容易出现。不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
解决方法:
a.专门的算法、原则
b.尽量减少同步资源的定义