通常使用锁方法(Synchronized修饰方法)、锁代码块(Synchronized块)、信号量(Semaphore)、Lock锁
1、要求线程a执行完才开始线程b, 线程b执行完才开始主线程
思路:由题意可知会有两条副线程a和b,编写好a,b的内容后,在主线程中启动两个线程。
关键点在于,一旦开启线程,线程的执行完全是由各自抢占cpu的能力而定,是人为不可控的,为了实现题目中的要求,我们需要在b线程中添加a.join()语句,便可以实现即使由b抢占到了cpu使用权,依旧会等a执行完,同样的方法在main方法中添加b.join()来实现main最后运行。
package com.work;
//1、要求线程a执行完才开始线程b, 线程b执行完才开始主线程
public class T1 {
public static void main(String[] args) {
Thread a=new Thread("A线程") {
@Override
public void run() {
for(int i=1;i<=10;i++)
{
System.out.println(getName()+":"+i);
}
}
};
Thread b=new Thread("b线程") {
@Override
public void run() {
try {
a.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=1;i<=10;i++)
{
System.out.println(getName()+":"+i);
}
}
};
a.start();
b.start();
try {
b.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=1;i<=10;i++)
{
System.out.println("main:"+i);
}
}
}
2、两个线程轮流打印数字,一直到100
思路:由题意知,需要准备两个副线程,以及一个共享的数字。
巧妙点在于,可以通过一个共享的布尔类型的变量flag来判断该那一个线程打印,当线程打印完立刻改变flag的状态,既可以实现交替打印。
package com.work;
//2、两个线程轮流打印数字,一直到100
public class T2 {
private static boolean flag=true;
private static int num=0;
public synchronized void print1()
{
while(true)//flag为true时打印
{
if(!flag)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num++);
flag=!flag;
this.notifyAll();
}
}
public synchronized void print2()
{
while(true)//flag为false时打印
{
if(flag)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num++);
flag=!flag;
this.notifyAll();
}
}
public static void main(String[] args) {
T2 obj=new T2();
new Thread(()->obj.print1()).start();
new Thread(()->obj.print2()).start();
}
}
3、写两个线程,一个线程打印1~ 52,另一个线程打印A~Z,打印顺序是12A34B...5152Z
思路:由题意可知,我们需要两个副线程实现交替打印,但是和上题的一一交替不同,这是二一交替,需要将线程A进行两次打印,再将线程B打印一次。
使用Synchronized实现时,我们可以使用一个公有标记count,让它具有三种状态(0,1,2),当数值为0时让线程B打印字符,当数值为1和2时让线程A打印数字。
使用Semaphore实现时,Semaphore的参数可以控制线程使用的通道,首先给予两个通道,允许两个只占用一个通道的线程通过,或者只允许一个占用两个通道的线程通过。由此可见,我们将打印数字的线程占用的通道数设置为1,打印字符的线程占用的通道数设置为2。通道使用完释放给B即可。
package com.work;
//3、写两个线程,一个线程打印1~ 52,另一个线程打印A~Z,打印顺序是12A34B...5152Z
public class T3 {
private int count = 2;
private int num = 1;
private char a = 'A';
// 打印数字
public synchronized void printNumble() {
for(int i=1;i<53;i++)
// while (true)
{
while (count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num++);
count--;// 输出两次
this.notifyAll();
}
}
// 打印数字
public synchronized void printChar() {
while (true) {
while (count != 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println((char) a++);
count += 2;
this.notifyAll();
}
}
public static void main(String[] args) {
T3 obj = new T3();
new Thread(() -> obj.printNumble()).start();
new Thread(() -> obj.printChar()).start();
}
}
使用Semaphore信号量实现:
package com.work;
import java.util.concurrent.Semaphore;
public class T3_2 {
Semaphore a=new Semaphore(2);
Semaphore b=new Semaphore(0);
char c='A';
// 打印数字
public void printNumble() {
try {
for(int i=1;i<=52;i++)
{
a.acquire(1);
System.out.println(i);
b.release(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印数字
public void printChar() {
try {
for(int i=1;i<=26;i++)
{
b.acquire(2);
System.out.println((char)c++);
a.release(2);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
T3_2 obj = new T3_2();
new Thread(() -> obj.printNumble()).start();
new Thread(() -> obj.printChar()).start();
}
}
4、编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC...
思路同上,区别只是循环的次数和阻塞线程唤醒线程的条件。
package com.work;
//4、编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC...
public class T4 {
private int flag=0;
public synchronized void printA() {
for (int i = 0; i < 5; i++) {
if(flag==1||flag==2)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
flag=1;
this.notifyAll();
}
}
public synchronized void printB() {
for (int i = 0; i < 5; i++) {
if(flag==0||flag==2)
{
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
flag=2;
this.notifyAll();
}
}
public synchronized void printC() {
for (int i = 0; i < 5; i++) {
if(flag==0||flag==1)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
flag=0;
this.notifyAll();
}
}
public static void main(String[] args) {
T4 obj=new T4();
//参数1:Runnable接口 参数2:线程名称
new Thread(()->obj.printA(),"线程A").start();
new Thread(()->obj.printB(),"线程B").start();
new Thread(()->obj.printC(),"线程C").start();
}
}
使用信号量Semaphore解题
package com.work;
import java.util.concurrent.Semaphore;
//4、编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC...
public class T4_2 {
private int flag = 0;
Semaphore a=new Semaphore(1);
Semaphore b=new Semaphore(0);
Semaphore c=new Semaphore(0);
public void printA() {
try {
for (int i = 1; i <= 5; i++) {
a.acquire();
System.out.println(Thread.currentThread().getName());
b.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void printB() {
try {
for (int i = 1; i <= 5; i++) {
b.acquire();
System.out.println(Thread.currentThread().getName());
c.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void printC() {
try {
for (int i = 1; i <= 5; i++) {
c.acquire();
System.out.println(Thread.currentThread().getName());
a.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
T4_2 obj = new T4_2();
// 参数1:Runnable接口 参数2:线程名称
new Thread(() -> obj.printA(), "线程A").start();
new Thread(() -> obj.printB(), "线程B").start();
new Thread(() -> obj.printC(), "线程C").start();
}
}
5、编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。
思路:由题意可知,需要十个线程,以及一个用于求和的变量。
这里可以使用创建线程的第三种方法,实现Callable接口实现,因为Callable的抽象方法可以返回一个值,我们可以通过接收该值进行求和运算,但是涉及到线程池,此处只简单放上代码供给参考。
package com.work;
//5、编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。
public class T5{
private static int total;
private static class SubThread extends Thread {
private int start;
private int end;
public SubThread(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
int sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
System.out.println(sum);
total+=sum;
}
}
public static void main(String[] args) {
int start = 1;
for (int i = 1; i <= 10; i++) {
int end = start + 9;
SubThread t = new SubThread(start, end);//System.out.println(start+","+end); //测试传入的参数是否正确
t.start();
start = end + 1;
try {
t.join(); //main线程让所有的子线程全部执行完了,才输出结果
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("最后的总和:"+total);
}
}
使用创建线程的第是那种方法(实现Callable接口):
package com.work;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
//编写10个线程,
//第一个线程从1加到10,
//第二个线程从11加20
//…第十个线程从91加到100,
//最后再把10个线程结果相加。
public class T5_2 {
//call方法,有返回值,和run方法的区别,在于可以把子线程做的任务最终的结果返回
private static class SubThread implements Callable<Long> {
private int start;
private int end;
public SubThread(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Long call() throws Exception {
long sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
return sum;
}
}
public static void main(String[] args) {
try {
//创建一个线程池,初始会有10个线程对象
ExecutorService pool = Executors.newFixedThreadPool(10);
System.out.println(pool);
long total=0;
int start = 1;
for (int i = 1; i <= 10; i++) {
int end = start + 9;
// System.out.println(start+","+end); //测试传入的参数是否正确
Future<Long> future=pool.submit(new SubThread(start, end));
start=end+1;
System.out.println(future.get());
total+=future.get();
}
System.out.println("最终的结果:"+total);
pool.shutdown();//关闭线程池
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
6、三个窗口同时卖票
思路和前面相同,用于熟悉线程。
package com.work;
//6 、三个窗口同时卖票
class Ticket{
String name;
int num=100;
public Ticket(String name, int num) {
super();
this.name = name;
this.num = num;
}
public void sale() {
while(true) {
synchronized (this) {
if(num<=0)
{
System.out.println("卖完了");
break;
}
else {
System.out.println(Thread.currentThread().getName()+"卖出了一张票");
System.out.println("剩余"+--num+"张票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Window implements Runnable{
private Ticket ticket;
public Window(Ticket ticket) {
super();
this.ticket = ticket;
}
@Override
public void run() {
ticket.sale();
}
}
public class T6 {
public static void main(String[] args) {
//创建一场电影票
Ticket t=new Ticket("hk",100);
new Thread(new Window(t),"窗口A").start();
new Thread(new Window(t),"窗口B").start();
new Thread(new Window(t),"窗口C").start();
}
}
7、生产者消费者
生产者消费者是线程中比较经典的模式。
思路:由题意知,我们至少需要两个线程:一个生产者一个消费者。生产者用于生产商品,消费者用于消费商品。我们还需要一个仓库,用于存储商品(存储object对象的list集合)。当仓库中没有商品时,唤醒生产者生产阻止消费,当仓库中放满商品时阻止生产唤醒消费。当未达到两个极端时,消费者和生产者各司其职互不干扰。
关键在于加锁的部位,始终记住,锁公共的部分。
Lock的使用效果和Synchronized类似,区别在于Lock的使用方法和lock(加锁)、unlock(释放锁)。
仓库类:
package com.****;
//仓库
import java.util.LinkedList;
public class Depot
{
//仓库的最大容量
public static final int MAX=10;
//生产者和消费者都使用这个仓库里的商品
public static final LinkedList<Object> list=new LinkedList<>();
//生产者使用的方法
public void profuce() {
synchronized (list) {
if(list.size()>=MAX)
{
//生产者进入阻塞状态
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Object());
System.out.println(Thread.currentThread().getName()+"生产了一个商品,存入list中,list中的元素个数为:"+list.size());
}
//唤醒所有在list中等待的线程
list.notifyAll();
}
}
//消费者使用的方法
public void consume()
{
synchronized (list) {
if(list.size()==0)
{
//消费者进入阻塞
try {
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else
{
list.remove();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"消费了一个商品,list中元素个数为:"+list.size());
}
//唤醒所有在list中等待的线程
list.notifyAll();
}
}
}
生产者:
package com.****;
//生产者
public class SCZ implements Runnable {
private Depot ck;
SCZ(Depot ck)
{
this.ck=ck;
}
@Override
public void run() {
//不断生产
while(true) {
ck.profuce();
}
}
}
消费者:
package com.****;
//消费者
public class XFZ implements Runnable{
private Depot ck;
XFZ(Depot ck)
{
this.ck=ck;
}
@Override
public void run() {
//不断消费
while(true) {
ck.consume();
}
}
}
测试类:
package com.****;
public class test {
public static void main(String[] args) {
Depot ck=new Depot();
//创建一个生产者
Thread t1=new Thread(new SCZ(ck),"生产者");
//创建一个消费者
Thread t2=new Thread(new XFZ(ck),"消费者");
t1.start();
t2.start();
}
}
使用Lock来实现生产者消费者模式:
唯一不同的是仓库类(其他的类和使用Synchronized一样):
package com.condition_lock;
//仓库
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Depot
{
//仓库的最大容量
public static final int MAX=10;
//生产者和消费者都使用这个仓库里的商品
public static final LinkedList<Object> list=new LinkedList<>();
private final Lock lock=new ReentrantLock();
//定义仓库状态
private final Condition empty=lock.newCondition();
private final Condition full=lock.newCondition();
//生产者使用的方法
public void profuce() {
lock.lock();//上锁
if(list.size()>=MAX)
{
//生产者进入阻塞状态
try {
full.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Object());
System.out.println(Thread.currentThread().getName()+"生产了一个商品,存入list中,list中的元素个数为:"+list.size());
}
//唤醒所有在list中等待的线程
empty.signal();
lock.unlock();
}
//消费者使用的方法
public void consume()
{
lock.lock();
if(list.size()==0)
{
//消费者进入阻塞
try {
empty.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else
{
list.remove();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"消费了一个商品,list中元素个数为:"+list.size());
}
//唤醒所有在list中等待的线程
full.signalAll();
lock.unlock();
}
}
8、交替打印两个数组
思路同上,区别在于占用信号量的个数,以及传递信号的条件。
使用信号量实现
package com.work;
import java.util.Arrays;
import java.util.concurrent.Semaphore;
//8、交替打印两个数组
public class T8 {
static int[] arr1=new int[10];
static int[] arr2=new int[10];
static {
int x=0;
int y=1;
for(int i=0;i<10;i++)
{
arr1[i]=x;x+=2;
arr2[i]=y;y+=2;
}
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.toString(arr2));
}
Semaphore arr11=new Semaphore(1);
Semaphore arr22=new Semaphore(0);
public void printArr1() {
try {
for(int i=0;i<arr1.length;i++)
{
arr11.acquire(1);
System.out.println(arr1[i]);
arr22.release(1);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void printArr2() {
try {
for(int i=0;i<arr2.length;i++)
{
arr22.acquire(1);
System.out.println(arr2[i]);
arr11.release(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
T8 obj=new T8();
new Thread(()->obj.printArr1(),"arr1").start();
new Thread(()->obj.printArr2(),"arr2").start();
}
}
使用Synchronized实现
package com.work;
import java.util.Arrays;
public class T8_2 {
static boolean flag=false;
static int[] arr1=new int[10];
static int[] arr2=new int[10];
static {
int x=0;
int y=1;
for(int i=0;i<10;i++)
{
arr1[i]=x;x+=2;
arr2[i]=y;y+=2;
}
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.toString(arr2));
}
public synchronized void printArr1() {
for (int i = 0; i < arr1.length; i++) {
if(flag)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(arr1[i]);
flag=true;
this.notifyAll();
}
}
public synchronized void printArr2() {
for (int i = 0; i < arr2.length; i++) {
if(!flag)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(arr2[i]);
flag=false;
this.notifyAll();
}
}
public static void main(String[] args) {
T8_2 obj=new T8_2();
//参数1:Runnable接口 参数2:线程名称
new Thread(()->obj.printArr1(),"线程Arr1").start();
new Thread(()->obj.printArr2(),"线程Arr2").start();
}
}