一,java多线程的两种创建方式
1,继承Thread类
继承Thread类,重写run()方法,run() 里面就是具体线程需要做的事(代码块),然后在主线程main线程中调用start()方法就可以实现线程。
public class TestThread {
//主线程
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
while (true) {
System.out.println(Thread.currentThread().getName());
System.out.println("兔子领先了,别骄傲");
}
}
}
//线程类
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println(this.getName());
System.out.println("乌龟领先了,加油");
}
}
}
2,实现Runnable接口
可以通过实现Runnable接口来创建线程
public class TestRunnable1 {
public static void main(String[] args) {
MyThread4 t4=new MyThread4();
Thread thread=new Thread(t4);
thread.start();
MyThread5 t5=new MyThread5();
Thread thread1=new Thread(t5);
thread1.start();
while (true) {
System.out.println(Thread.currentThread().getName());
System.out.println("兔子领先了,别骄傲");
}
}
}
//线程接口的实现类
class MyThread4 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName());
System.out.println("乌龟领先了,别骄傲");
}
}
}
//线程接口的实现类
class MyThread5 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName());
System.out.println("大象领先了,加油");
}
}
}
也可以通过内部类的方式来创建线程
public class TestRunnable2 {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
System.out.println("乌龟领先了,别骄傲");
}
}
});
thread.start();
Thread thread1=new Thread(new Runnable() {
public void run() {
while (true) {
try {
//Thread.sleep(2000);
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
System.out.println("大象领先了,加油");
}
}
});
thread1.start();
while (true) {
System.out.println(Thread.currentThread().getName());
System.out.println("兔子领先了,别骄傲");
}
}
}
3,线程常用方法
- stop() 强行终止线程,让出cpu资源,但已过期,不推荐使用。
- interrupt() 中断线程。
- interrupted() 判断当前线程是否中断
- isterruoted() 判断当前对象线程是否中断
- sleep() 线程休眠,让出CPU资源。
- yieid() 与sleep相似,但是不能由用户指定时间,并且yieid()方法只能出让给同优先级的线程机会。
- join() 可以让原来并行执行的线程顺序执行,比如在B线程中调用A线程的join()方法,会使B线程等待A线程执行完毕 后继续执行。
- wait() 等待 notify()通知其他线程 notifyAll()通知所有的其他线程。这三个方法会在线程间的通信中具体介绍的。
二,线程的生命周期
三,线程的优先级和同步
一般来说线程越高的得到的CPU资源就越多,我们虽然说是并发,但是真正执行的程序的只有一个cpu,所以达不到真正的并行,只是我们在使用的时候用户感觉是并行执行的。
main主线程的优先级是默认的5,其他线程可以手动设置setPriority(),数值为1-10 数字越高,优先级越高。查看优先级可以用getPriority()方法。
线程的安全问题,多个线程去操作一个共享数据的时候,有可能出现同时操作一个共享数据的情况,这时候就会造成错误,所以为了避免这种错误,我们可以对这个操作加一个同步锁synchronized。
可以在线程调用的方法上加上synchronized关键字,让一个线程调用这个加了同步锁的方法的时候,其他线程不能调用这个方法。
import java.util.Vector;
public class TestSyn1 {
public static void main(String[] args) {
//真实角色
PlaneBillA web = new PlaneBillA();
//代理角色
Thread thread1 = new Thread(web,"农民");
Thread thread2 = new Thread(web,"工人");
Thread thread3 = new Thread(web,"学生");
thread1.start();
thread2.start();
thread3.start();
}
}
class PlaneBillA implements Runnable{
private int num=100;//总共10张票
private boolean flag = true;
@Override
public void run() {
while(flag){
// 0 -1
//出现重复的票 num=1000设置大一点就出现
//线程不安全,数据不准确:结果有-1值,重复值
function();
}
}
//1、线程不安全
public synchronized void function(){
if (num<=0) {
flag = false;
return;//跳出循环,结束
}
try {
Thread.sleep(500); //模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}
synchronized除了可以用在方法上,还可以用在一段代码段中,作用相同,防止其他的线程来访问,这个过程就叫做线程同步,或者线程互斥。
public class TestSyn2 {
public static void main(String[] args) {
//真实角色
PlaneBillB web = new PlaneBillB();
//代理角色
Thread thread1 = new Thread(web,"农民");
Thread thread2 = new Thread(web,"工人");
Thread thread3 = new Thread(web,"学生");
thread1.start();
thread2.start();
thread3.start();
}
}
class PlaneBillB implements Runnable{
private int num=100;//总共10张票
private boolean flag = true;
@Override
public void run() {
while(flag){
// 0 -1
//出现重复的票 num=1000设置大一点就出现
//线程不安全,数据不准确:结果有-1值,重复值
function();
}
}
//1、线程不安全
public void function(){
String string="hello";
//synchronized (this)
synchronized (string)
{
if (num<=0) {
flag = false;
return;//跳出循环,结束
}
try {
Thread.sleep(500); //模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}
}
四,线程之间的通信
线程之间的通信主要依靠同步,但有时候有一些特殊的情况,比如我们常见的生产者,仓库和消费者模型,消费者消费时需要生产者提供生产物到仓库,而生产者生产好了之后要通知消费者。在这里需要用到三个方法,wait(),notify(),notifyall()。
wait() 中断方法的执行,使本线程等待,暂时出让CPU资源,并允许其他线程使用这个同步方法。
notify() 唤醒由于使用这个同步方法而进入等待的线程。
notifyall() 唤醒由于使用这个同步方法而进入等待的所有线程。
下面我们以生产者 仓库 消费者模型来举例说明线程之间的通信。
1 SharedData.java 共享仓库,有生产同步方法 putShareChar 和消费同步方法 getShareChar()。
//共享数据类
public class SharedData{
private char c;
private boolean isProduced = false; // 信号量
// 同步方法putShareChar()
public synchronized void putShareChar(char c) {
// 如果产品还未消费,则生产者等待
if (isProduced) {
try{
System.out.println("消费者还未消费,因此生产者停止生产");
wait(); // 生产者等待,终止程序
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.c = c;
isProduced = true; // 标记已经生产
notify(); // 通知消费者已经生产,可以消费
System.out.println("生产了产品" + c + " 通知消费者消费...");
}
// 同步方法getShareChar()
public synchronized char getShareChar() {
// 如果产品还未生产,则消费者等待
if (!isProduced){
try{
System.out.println("生产者还未生产,因此消费者停止消费");
wait(); // 消费者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isProduced = false; // 标记已经消费
notify(); // 通知需要生产
System.out.println("消费者消费了产品" + c + " 通知生产者生产...");
return this.c;
}
}
2 Consumer.java 消费者类,它会调用仓库中的同步消费方法 getShareChar()。
//消费者线程
public class Consumer extends Thread {
private SharedData s;
Consumer(SharedData s){
this.s = s;
}
public void run(){
char ch;
do {
try {
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
ch = s.getShareChar(); // 从仓库中取出产品
} while (ch != 'D');
}
}
3 Producer.java 生产者类,它会调用同步生产方法 setShareChar()。
//生产者线程
public class Producer extends Thread {
private SharedData s;
Producer(SharedData s){
this.s = s;
}
public void run(){
for (char ch = 'A'; ch <= 'D'; ch++){
try{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
s.putShareChar(ch); // 将产品放入仓库
}
}
}
下面我们初始化这两个线程,让它可以运行起来
public class TestProducerConsumer {
public static void main(String[] args) {
//共享同一个共享资源
SharedData s = new SharedData();
//生产者线程
Producer producer=new Producer(s);
producer.start();
//消费者线程
Consumer consumer=new Consumer(s);
consumer.start();
}
}
从运行的结果可以看出,无论是生产者线程还是消费者线程当共享数据不满足条件时,都会进入wait()方法,同时电泳notify()来通知其他线程执行相关操作,这就是线程之间的通信方法,简单又使用,MQ就是使用了这种方法来提高代码的执行效率。
五 定时任务
定时器实际上就是一个线程,我们在满足条件的时候调用就可以了。
定时任务类
import java.util.TimerTask;
public class CustTimeTasker extends TimerTask{
@Override
public void run() {
System.out.println("TimerTask,定时执行任务");
}
}
调用定时任务
import java.util.Timer;
public class TestTask {
/**
* @param args
*/
public static void main(String[] args) {
//定时器
Timer timer=new Timer();
CustTimeTasker cust=new CustTimeTasker();
//从当前时间开的,10秒后执行一次
//timer.schedule(cust,1000*10);
//从当前时间开的,3秒后执行 每10秒执行一次,循环执行
//timer.schedule(cust,3000,1000*10);
//从当前时间开的,执行一次
//timer.schedule(cust, new Date());
//从当前时间开的, 每10秒执行一次,循环执行
timer.schedule(cust, new Date(), 1000*10);
}
}
定时任务实际上也属于多线程的内容,所以这里一代而过,关于一些多线程的常用API,可以从这篇文章学习到,如有错误或者理解不深刻的地方,欢迎指出,共同进步。