Java——多线程学习笔记(下)
产生线程的方法:
(1)继承Thread类,重写run函数,调用start函数启动线程
(2)某一个类实现Runnable接口,然后该类作为参数传入一个Thread类实例,再调用该Thread类实例的start函数启动线程
(3)JDK5的新方法,实现Callable接口 ,此方需要用到FutureTask类
例子:
myCall mc = new myCall(0);
FutureTask futureTask = new FutureTask(mc);
new Thread(futureTask).start();
//实现了Callable的myCall类
class myCall implements Callable {
private int sum;
public myCall(int sum) {
this.sum = sum;
}
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
(4)使用线程池
注意点
一个线程只有一个开始
Thread myThread = new Thread();
myThread.start();
myThread.start();//这样会报错
同时如果直接调用线程对象的run()方法,不会产生新的线程。
yield()是当前线程释放 cpu 资源,重新与其他线程争抢,结果不一。
join() 在线程a中调用线程b的join()会使得a进入阻塞状态,等到线程b完全执行完毕后,线程a才会继续执行。
sleep()线程睡眠
isAlive()判断当前线程是否存活
线程安全问题
如三个线程卖票,会出现重票、错票等问题
原因:某一线程操作未完成时另一个线程进入操作,导致重票错票。
解决方法:当一个线程在操作过程中,其他线程无法参与(即使该线程在阻塞状态中),直到该线程操作结束,其他线程才能参与。
在JAVA中通过同步机制解决线程安全问题。
方法一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量
3.同步监视器,俗称:锁。任何一个类的对象都可以充当锁。一般用当前对象。要求:多个线程必须要共用同一把锁。
方法二:同步方法
1.依然涉及到同步监视器,只是不需要显式声明,依然需要注意多个线程必须要共用同一把锁。。
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
同步的好处是解决了线程的安全问题。但操作同步代码时,只能有一个线程参与,其他线程只能等待,类似于单线程,效率有所下降。
方法三:
JDK5的新方法,用ReentrantLock类实现线程同步。具体:
1.先实例化一个ReentrantLock对象
2.调用锁定方法lock()
3.调用解锁方法unlock()
例子:
public void run() {
while(true){
try {
lock.lock();
if (tickets > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tickets);
} else {
break;
}
tickets--;
} finally {
lock.unlock();
}
}
}
synchronized和Lock的异同
相同:两者都能解决线程安全问题
不同:synchronized机制在执行完同步代码后,自动释放同步监视器。
Lock需要手动启动同步(lock()),同时手动结束同步(unlock())。
线程通信问题
wait()方法会释放当前线程的同步监视器,线程进入阻塞状态
notify()会唤醒被wait()的线程,多个线程会唤醒优先级最高的线程。
notifyAll()会唤醒所有被wait()的线程
以上三个方法都必须使用在同步代码块或者同步方法中。
同时这三个方法的调用者必须是同步代码块或者同步方法中的同步监视器
sleep()和wait()都能使当前线程进入阻塞状态。