在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。
一、线程基本操作:
1、多线程的两种实现方法
1、继承Thread类 2、实现Runnable接口 相比继承Thread类,实现Runnable接口的好处: 1、避免了java单继承的局限性 2、适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据分离,较好的体现了面向对象的设计思想
2、线程的基本操作
Thread.currentThread().getName() |
取得当前线程的名称 |
new Thread(myRunnable,“线程名”) setName(“线程名”) |
设置线程的名字 |
setDaemon() | 后台线程 在 Java 程序中,只要前台有一个线程在运行,则整个 Java 进程都不会消失,所以此时可以设置一个后台线程,这样即使 Java 线程结束了,此后台线程依然会继续执行,要想实现这样的操作,直接使用 setDaemon() 方法即可。 |
sleep(毫秒数) | 线程休眠 |
interrupt() | 线程中断 当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。 |
setPriority(优先级) | 设置线程优先级 MIN_PRIORITY(优先级最低) MAX_PRIORITY(优先级最高) NORM_PRIORITY(优先级中等) |
3、线程中断测试
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。
class MyThread1 implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
System.out.println("1、进入run()方法") ;
try{
Thread.sleep(10000) ; // 线程休眠10秒
System.out.println("2、已经完成了休眠") ;
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
return ; // 返回调用处
}
System.out.println("4、run()方法正常结束") ;
}
};
public class MyTest{
public static void main(String args[]){
MyThread1 mt = new MyThread1() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
try{
Thread.sleep(2000) ; // 线程休眠2秒
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
}
t.interrupt() ; // 中断线程执行
}
};
二、数据不同步问题解决
测试类中,有一个静态成员a,定义2000个子线程,分别执行对静态变量a的++操作,然后在主线程中输出累加后的a值
MyRunnable:
public class MyRunnable implements Runnable{
Object lock = new Object();
@Override
public void run() {
synchronized (lock) {
MyTest.a++;
}
}
}
MyTest:
public class MyTest {
public volatile static int a = 0;
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(myRunnable);
t.start();
}
System.out.println("a = " + a);
}
}
以上代码,在输出时,出现数据不同步,理论上该输出2000,结果输出一个未知大小的值,问题分析:
在子线程执行过程中,主线程也参与进来,导致主线程过早的从内存中读取了a的值,导致a的值输出错误
解决办法1:
使用延时函数,延迟主函数的执行
public class MyTest {
public static int a = 0;
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(myRunnable);
t.start();
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a);
}
}
解决办法2:
使用volatile关键字对变量a进行修饰,强制每次从内存中读取a的值
public class MyTest {
public volatile static int a = 0;
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(myRunnable);
t.start();
}
while(true) {
if(a == 2000) {
break;
}
}
System.out.println(a);
}
}
解决办法3:
使用闭锁,2000个线程分别配备一把锁,等待所有的锁释放后再进行主线程输出操作
MyTest:
public class MyTest {
public static int a = 0;
//增加2000把闭锁
public static CountDownLatch countDownLatch = new CountDownLatch(2000);
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(myRunnable);
t.start();
}
try {
//等待所有的闭锁释放完毕
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a);
}
}
MyRunnable:
public class MyRunnable implements Runnable{
Object lock = new Object();
@Override
public void run() {
synchronized (lock) {
MyTest.a++;
//释放该线程的闭锁
MyTest.countDownLatch.countDown();
}
}
}
三、卖票案例
SellTicket:
public class SellTicket implements Runnable {
private int tickets = 100;
//任意定义一个引用对象,充当锁旗标,单次只允许一个对象拿到该锁
private Object lock = new Object();
@Override
public void run() {
while(tickets > 0) {
synchronized (lock){
if(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
}
SellTicketDemo:
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
//定义三个Thread对象,把SellTicket对象作为构造方法参数,并给出窗口名字
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}