本篇文章只是对多线程做一个简单较全面了解,并不深入探讨
什么时候需要使用多线程?
1、当程序需要同时执行两个或多个任务时。
2、程序需要实现一些需要等待的任务时,如用户输入、文件读写
操作、网络操作、搜索等。
3、需要一些后台运行的程序时。
方式一:继承Thread类
class Even extends Thread{
@Override
public void run(){
for(int i=0; i<10;i++){
if(i%2 != 0)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
方式二:实现Runnable接口
class Odd implements Runnable{
@Override
public void run() {
for(int i=0; i<10;i++){
if(i%2==0)
System.out.println(Thread.currentThread().getName()+":"+i);
Thread.yield(); //让步
/*try {
Thread.sleep(100); //休眠
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
public class ThreadDome01 {
public static void main(String[] args) {
Even even = new Even();
even.start();
Odd odd = new Odd();
Thread thread = new Thread(odd);
thread.start();
}
}
/* [补充]:1.在run()执行完之后,线程就死亡了
* 2.even.start()已经创建了一个线程,不能再写even.start(),如果要再创建
* 线程,就必须再new一个对象
*/
输出结果:
Thread-1:0
Thread-0:1
Thread-0:3
Thread-1:2
Thread-0:5
Thread-1:4
Thread-0:7
Thread-1:6
Thread-0:9
Thread-1:8
推荐使用实现Runnable的方式:
新增方式一:使用线程池
线程池相关API:
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
< T> Future< T > submit(Callable< T> task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
java SE5的Java.util.concurrent包中的执行器可以为我们管理Thread对象,从而简化并发编程.Executor无须显示地管理线程的生命周期。
class Ex3RunnerA implements Runnable {
public Ex3RunnerA() {
System.out.println("Constructing Ex3RunnerA");
}
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println("Hi from Ex3RunnerA");
Thread.yield();
}
System.out.println("Ex3RunnerA task complete.");
// return;
}
}
class Ex3RunnerB implements Runnable {
public Ex3RunnerB() {
System.out.println("Constructing Ex3RunnerB");
}
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println("Hi from Ex3RunnerB");
Thread.yield();
}
System.out.println("Ex3RunnerB task complete.");
//return;
}
}
class Ex3RunnerC implements Runnable {
public Ex3RunnerC() {
System.out.println("Constructing Ex3RunnerC");
}
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println("Hi from Ex3RunnerC");
Thread.yield();
}
System.out.println("Ex3RunnerC task complete.");
//return;
}
}
public class Ex3 {
public static void main(String[] args) {
// ExecutorService对象是通过Executors的静态方法创造的
ExecutorService exec1 = Executors.newCachedThreadPool();
exec1.execute(new Ex3RunnerA());
exec1.execute(new Ex3RunnerB());
exec1.execute(new Ex3RunnerC());
exec1.shutdown();
ExecutorService exec2 = Executors.newFixedThreadPool(3);
exec2.execute(new Ex3RunnerA());
exec2.execute(new Ex3RunnerB());
exec2.execute(new Ex3RunnerC());
exec2.shutdown();
ExecutorService exec3 = Executors.newSingleThreadExecutor();//就像是线程数为1的FixedThreadPool
exec3.execute(new Ex3RunnerA());
exec3.execute(new Ex3RunnerB());
exec3.execute(new Ex3RunnerC());
exec3.shutdown();
}
}
新增方式二:实现Callable接口
与使用Runnable相比, Callable功能更强大些
class TaskWithResult implements Callable<String> {
private int id;
public TaskWithResult(int id){
this.id = id;
}
@Override
public String call(){
return "result of TaskWithResult "+id;
}
}
public class Ex5 {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results= new ArrayList<>();
for(int i=0; i<10;i++){
results.add(exec.submit(new TaskWithResult(i)));//submit()方法会产生Future对象
}
for(Future<String> fs : results){
try {
System.out.println(fs.get());//返回的结果
}catch (InterruptedException e){
System.out.println(e);
return;
}catch (ExecutionException e){
System.out.println(e);
return;
}finally {
exec.shutdown();
}
}
}
}
submit()方法会产生Future对象,它用Callable返回的结果的特定类型进行了参数化,可以用isDone()方法来查询Future是否已经完成。当完成任务时,它具有一个结果,可以调用get()来获取该结果。
输出结果:
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 2
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
result of TaskWithResult 9
首先举个例子——模拟火车站售票程序,开启三个窗口售票。
class Ticket implements Runnable {
private int tick = 100;
public void run() {
while (true) {
if (tick > 0) {
try {
Thread.sleep(100); //将线程安全问题展现更清楚
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread
().getName() + "售出车票,tick号为:" + tick--);
} else
break;
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
部分输出结果:
t2窗口售出车票,tick号为:4
t1窗口售出车票,tick号为:4
t3窗口售出车票,tick号为:3
t1窗口售出车票,tick号为:3
t2窗口售出车票,tick号为:1
t3窗口售出车票,tick号为:2
问题:多个窗口售票出了相同的票号。
问题原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
防止线程冲突的方法就是当资源被一个任务使用时,在其上加上锁。都一般采取序列化访问共享资源 的方案。
方式一:同步代码块
* synchronized(同步监视器){
* //需要同步的代码
* }
* 说明:1、需要同步的代码即使操作共享数据的代码
* 2、共享数据:多个线程共同操作的变量,在本例子中tick
* 3、同步监视器,俗称:锁。任何一个类的对象,都可以充当索。
* 要求:多个线程必须要共用同一把锁。
使用同步代码块的方式来改改上面的火车卖票的程序。
下面是实现Runnable接口的方式
class Ticket implements Runnable {
private int tick = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
// synchronized (this){ //对于实现同步监视器是当前对象t。一般都写this
synchronized (obj) { //同步监视器是obj,任何对象都可以当作索。但必须多个线程共用一把锁
if (tick > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread
().getName() + "售出车票,tick号为:" + tick--);
} else
break;
}
// }
}
}
}
需要注意的是同步监视器(也就是锁),同步监视器可以是任何对象,但必须是多个线程共用一把锁。对于实现Runnable接口的方式,上面两个都可以,一般用synchronized (this){},
下面是对于继承的方式
继承的方式和实现接口的方式是有所不同的,
由于继承方式需要创建3个Ticcket1对象,所以不能用synchronized (this){},
可以将Ticket1类作为同步监视器,即 synchronized (Ticket1.class){ },也可以用其它对象,但是需要声明为静态static的。
class Ticket1 extends Thread {
private static int tick = 100;
// private static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (Ticket1.class){ //和下面的都可以
// synchronized (obj) { //同步监视器是obj,
if (tick > 0) {
try {
Thread.sleep(100); //将线程安全问题展现更清楚
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread
().getName() + "售出车票,tick号为:" + tick--);
} else
break;
}
// }
}
}
}
class TicketDemo02 {
public static void main(String[] args) {
Ticket1 t1 = new Ticket1();
Ticket1 t2 = new Ticket1();
Ticket1 t3 = new Ticket1();
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
总的来说就是同步监视器需要是多个线程共用一个
同步方法就是如果操作共享资源的刚好是一个方法,即可以在方法上加上synchronized 关键字就行。
比如对于实现的方式
public synchronized void method(){}
对于继承的方式 需要加上static。
public static synchronized void show(){}
对于同步方法的总结:
javaSE5的Java.util.concurrent类库还包含有定义在java.util.concurrent.locks中的显示的互斥机制。Lock对象必须显示地创建、锁定和释放。因此,它与内建的锁形式相比。代码缺少点优雅,但是对于某些问题更有优势。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的
工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象
加锁,线程开始访问共享资源之前应先获得Lock对象。
class A{
private ReentrantLock lock = new ReenTrantLock();
//private Lock lock = new ReenTrantLock();//也可以
public void m(){
lock.lock();
try{
//保证线程安全的代码;
}
finally{
lock.unlock();
}
}
}
//注意:如果同步代码有异常,要将unlock()写入finally语句块
注意 如果方法有返回值,return必须在try子句中出现,以确保unlock()不会过早发生。
还是以一个例子来讲解:使用两个线程打印 1-100。线程1, 线程2 交替打印
class Communication implements Runnable {
int i = 1;
public void run() {
while (true) {
synchronized (this) {
notify();//因为省略了this,相当于this.notify(),必须是同步监视器调用。
if (i <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + i++);
} else
break;
try {
wait();//省略了this,相当于this.wait(),必须是同步监视器调用。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Communication c = new Communication();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
涉及到的3个方法:
说明
这三个方法必须使用在同步代码块或同步方法中。
这三个方法的调用者必须是同步代码块或同步方法中的同步监视器
否则都会报java.lang.IllegalMonitorStateException异常。
如果同步监视器是其它对象:
private Object obj = new Object();
//省略代码...
synchronized (obj) {//obj作为同步监视器
obj.notify();
//省略代码...
obj.wait();
//....
}
正因为如此,这三个方法声明在Object类中。
再来说一说wait()和sleep()的区别:
通过上面的学习,可以敲一下多线程的经典问题来学以致用。
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,
店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;
如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
代码:
class Clerk { // 售货员
private int product = 0;
public synchronized void addProduct() {
if (product >= 20) {
try {
wait();
} catch (InterruptedException e)
{
e.printStackTrace();
}
} else {
product++;
System.out.println("生产者生产了 第" + product + "个产品");
notifyAll();
}
}
public synchronized void getProduct() {
if (this.product <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("消费者取走了第" +
product + "个产品");
product--;
notifyAll();
}
}
}
class Productor implements Runnable { // 生产者
Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("生产者开始生产产品");
while (true) {
try {
Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer implements Runnable { // 消费者
Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("消费者开始取走产品");
while (true) {
try {
Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.getProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Thread productorThread = new Thread(new Productor(clerk));
Thread consumerThread = new Thread(new Consumer(clerk));
productorThread.start();
consumerThread.start();
}
}
资料:宋红康java多线程讲解
如果读者大大们感觉写的可以,希望给个小小的点赞,你们的鼓励就是我前进的动力。