线程的死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
解决方法
1.专门的算法、原则
2.尽量减少同步资源的定义
3.尽量避免嵌套同步
例
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1=new StringBuffer();
StringBuffer s2=new StringBuffer();
new Thread() {
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() {
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(100);导致第一个线程拿到锁s1时停留短暂的时间,在这短时间内第二个线程那到了锁s2,接下来第一个线程需要锁s2才能执行下去,而第二个线程则需要锁s1才能执行下去,导致两个线程僵持不下,形成死锁局面。
当不加入Thread.sleep(100)时,程序也有概率会出现死锁局面,只是加入后使得出现死锁的概率大大增加了。
Lock锁解决线程同步安全问题
Lock实际是一个接口。
import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable{
private int ticket=100;
//1.实例化ReentrantLock
private ReentrantLock lock=new ReentrantLock(true);
//true表示公平的意思,就是假如三条线程先后进来,则按顺序进入同步资源
//先进先出原则,若括号内不写true,则默认是false
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 {
lock.unlock();//3.调用解锁方法
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Window w=new Window();
Thread t1=new Thread(w);
Thread t2=new Thread(w);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
面试题:synchronized与lock的异同?
同:二者都可以解决线程的安全问题
不同:synchronize机制在执行完相应的同步代码后,自动释放同步监视器。
Lock需要手动启动同步(lock()),同时结束同步也需要手动实现(unlock())。
其实用哪种方法解决线程安全都行都行。
面试题:如何解决线程安全问题?有几种方式?
三种:synchronize两种(同步代码块、同步方法),还有一种Lock。
例:
import java.util.concurrent.locks.ReentrantLock;
/*
解决银行存钱两个人同时操作一个账户线程安全问题
分析:
是否是多线程?
是否有共享数据?
是否有线程安全问题?
需要考虑如何解决线程安全问题?同步机制:三种方法
*/
class Account{
private double balance;
public Account(double balance){
this.balance=balance;
}
private ReentrantLock lock=new ReentrantLock(true);
//存钱
public void deposit(double amt){
try{
lock.lock();
if(amt>0){
balance+=amt;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
}
}finally {
lock.unlock();
}
}
}
class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct=acct;
}
@Override
public void run() {
for(int i=0;i<3;i++){
acct.deposit(1000);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Account acct=new Account(0);
Customer c1=new Customer(acct);
Customer c2=new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
注意,多线程的lock锁也必须是同一个锁,具体情况具体分析需不需要加上static。
线程通信的例题
前提:wait()方法导致线程进入阻塞状态的同时会释放同步锁。
notify():唤醒一个线程,若有多个线程处于wait(阻塞)状态,则先唤醒优先级更高的线程,同级别则随机唤醒。
notifyAll():唤醒所有wait(阻塞)状态的线程。
sleep()导致先暂停一会儿,但不会释放同步锁。
wait,notify,notifyAll方法都必须使用在同步方法块或同步方法中,且这些方法的调用者都是当前的同步监视器。
class Number implements Runnable{
private int number=1;
@Override
public void run() {
while (true){
synchronized (this){
notify();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(number<=100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
}else {
break;
}
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Number n = new Number();
Thread h1=new Thread(n);
Thread h2=new Thread(n);
h1.setName("线程一");
h2.setName("线程二");
h1.start();
h2.start();
}
}
面试题:
sleep()和wait()方法的异同:
相同:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同:
1.两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2.调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块中调用。
3.关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,但是wait会释放。
例:
实现柜台进货售货
//柜员
class Clerck{
private int num=0;
public synchronized void produce(){
if(num<20){
num++;
System.out.println(Thread.currentThread().getName()+"开始生产第:"+num+"个产品");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void buy(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"开始购买第:"+num+"个产品");
num--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer extends Thread{
private Clerck c;
public Producer(Clerck c){
this.c=c;
}
@Override
public void run() {
while (true){
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
c.produce();
}
}
}
//消费者
class Consummer extends Thread{
private Clerck c;
public Consummer(Clerck c){
this.c=c;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
c.buy();
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Clerck c=new Clerck();
Consummer h=new Consummer(c);
Producer p=new Producer(c);
h.setName("消费线");
p.setName("生产线");
h.start();
p.start();
}
}
JDK 5.0新增线程创建方式
新增方式一:实现Callable接口
与使用Runnable相比,Callable功能更强大些。
1.相比run()方法,可以有返回值
2.方法可以抛出异常
3.支持泛型的返回值
4.需借助Future Task类,比如获取返回结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
打印出1-10的数,并且求和输出
*/
//1.创建一个实现Callable的实现类
class numThread implements Callable {
private int num=0;
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
for(int i=0;i<10;i++){
System.out.println(i);
num+=i;
}
return num;
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
numThread numThread = new numThread();
//4.将此Callable接口实现类的对象作为传递到FutureTack构造器中
//创建FutureTask的对象
FutureTask futureTask=new FutureTask(numThread);
//将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象并调用start
new Thread(futureTask).start();
try {
//6.获取Callable中的call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object num=futureTask.get();
System.out.println("总和为:"+num);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
1.call()可以有返回值。
2.call()可以抛出异常,被外面的操作捕获,获取异常信息。
3.Callable是支持泛型的。
新增方式二:使用线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class numThread implements Runnable {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service= Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1=(ThreadPoolExecutor) service;
//设置线程池的属性
//System.out.println(service.getClass());
service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new numThread());//适用于Runnable
//service.submit();适用于Callable
service.shutdown();//3.关闭线程池
}
}
面试题:创建多线程有四种方式。