Java多线程(一)
线程(thread)是一个程序内部的一条执行流程。如果只有一条执行流程,那么这个程序就是单线程的程序,例如只有main方法的一个程序。多线程是从软硬件上实现多条执行流程的技术,在各种通信、购物等系统上都有多线程计算的应用。1. 多线程的创建通常来说我们创建多线程有三大方法:继承Thread类、实现Runnable接口、实现Callable接口。2. Thread常用API获取线程名称getName
线程(thread)是一个程序内部的一条执行流程。如果只有一条执行流程,那么这个程序就是单线程的程序,例如只有main方法的一个程序。
多线程是从软硬件上实现多条执行流程的技术,在各种通信、购物等系统上都有多线程计算的应用。
多线程的创建
通常来说我们创建多线程有三大方法:继承Thread类、实现Runnable接口、实现Callable接口。
继承Thread类
我们可以写一个子类MyThread继承线程类Thread,重写run()方法。然后创建MyThread类的对象,调用线程对象的start()方法启动线程。
这种方法实现简单,但是线程类已经继承Thread,无法继承其他类,不利于扩展。
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("子线程 "+i);
}
}
}
Thread t=new MyThread();//子线程对象
t.start();//启动线程
//主线程的内容写后面
实现Runnable接口
我们写一个线程任务类MyRunnable实现Runnable接口,重写run()方法。
class myRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("子线程 "+i);
}
}
}
然后创建MyRunnable任务对象,把MyRunnable任务对象交给Thread处理。调用线程对象的start()方法启动线程。这里出现了Thread的构造器,Thread(Runnable target)、
public Thread(Runnable target ,String name )指定线程名称。
Runnable work= new myRunnable();//创建MyRunnable任务对象
Thread t=new Thread(work);
t.start();
//主线程的内容写后面
线程任务类只是实现接口,可以继续继承类和实现接口,但是多一层对象包装,线程执行结果不可以直接返回。
//简化:
new Thread(() -> {
for (int i = 0; i < 200; i++) {
System.out.println("Lambda 子线程 "+i);
}
}).start();
实现Callable接口 ——可以得到线程执行的结果
前2种线程创建方式都存在一个问题,重写的run方法均不能直接返回结果。
步骤1、得到任务对象
定义类实现Callable接口,重写call方法,封装要做的事情。
class myCallable implements Callable<String>{
private int n;
public myCallable(int n){
this.n=n;
}
@Override
public String call() throws Exception {
int sum=0;
for (int i = 0; i < n; i++) {
System.out.println("任务进行中: "+i+"/"+n);
sum+=i;
}
System.out.println("任务完成: "+n);
return "子线程计算1+...+n的值:"+sum;
}
}
用FutureTask把Callable对象封装成线程任务对象。(FutureTask实现了Runnable的对象,可以给Thread,可以使用get方法获得返回值)
Callable<String> call=new myCallable(190);
FutureTask<String> futureTask=new FutureTask<>(call);
//2
Thread t=new Thread(futureTask);
t.start();
//3
try {//如果线程任务没有完成会在这里等待
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
步骤2、把线程任务对象交给Thread处理,调用Thread的start方法启动线程
步骤3、FutureTask的get方法可以获取任务执行的结果。
Thread常用API
获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()。
Thread类的线程休眠方法 public static void sleep(long time):Thread.sleep(5);//毫秒
线程安全
多个线程同时操作同一个共享资源的时候可能会出现安全问题。
多线程同时访问并修改共享资源:例如,有一个公共的账户有1万元。如果A和B同学同时来取1万元,由于AB是两个线程操作,可能存在都取出情况。
public static void main(String[] args) {
account acc=new account("happy",10000);
new getMoneyThread(acc,"张三").start();
new getMoneyThread(acc,"李四").start();
}
//账户类
class account{
private String ID;
private double money;
public account(){}
public account(String id,double money){
this.ID=id;
this.money=money;
}
public void getMoney(double money){
String name=Thread.currentThread().getName();
//取钱
if(this.money>=money){
System.out.println(name+"取出"+money);
this.money-=money;
System.out.println(ID+"剩余"+this.money);
}
else
System.out.println(name+"取钱"+money+" 失败"+"剩余"+this.money);
}
public String getID() {
return ID;
}
public double getMoney() {
return money;
}
}
//取钱的线程类
class getMoneyThread extends Thread{
private account a;
public getMoneyThread(account a,String name){
super(name);
this.a=a;
}
@Override
public void run() {
a.getMoney(10000);
}
}
因为在取钱的时候,首先判断是否有足够的余额,两个线程同时进来,就可能导致判断都通过,继而后面的操作都通过,使得两个人都取到了钱。
线程同步
上面的例子中,多个线程同时执行,发现账户都是可以取钱的,而实际上不可能发生这样的事情。这就是为了解决线程安全问题,采用了线程同步,让多个线程实现先后依次访问共享资源。
线程同步的核心思想:加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。(操作系统中的进程互斥,对某个系统资源,一个进程正在使用它,另外一个想用它的进程就必须等待,而不能同时使用 。)
方法一:同步代码块,对出现问题的核心代码使用synchronized进行加锁,每次只能一个线程占锁进入访问。
方法二:同步方法,把出现线程安全问题的核心方法给上锁。
方法三:Lock锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock是接口不能直接实例化,采用它的实现类ReentrantLock来构建Lock锁对象。lock()、unlock()分别获得锁和释放锁。
更多推荐
所有评论(0)