1. 线程的3种创建方式
(1) 继承Thread类,重写run()方法
public class FirstThread extends Thread {
private int i ;
public void run(){
for(i = 0; i < 100; i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args){
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 20){
new FirstThread().start();
new FirstThread().start();
}
}
}
}
(2)实现Runnable接口创建线程类,重写run()方法,jdk1.0, 无返回值
public class SecondThread implements Runnable {
private int i;
@Override
public void run() {
for (; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for ( int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if ( i == 20) {
SecondThread st = new SecondThread();
new Thread(st, "线程1").start(); //Runable对象仅仅作为Thread对象的target,实际的线程对象仍然是Thread示例,
new Thread(st, "线程2").start(); //该Thread线程负责执行其target的run()方法。
}
}
}
}
(3)使用Callable和Future创建线程,重写call方法,jdk1.5, 有返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThirdThread {
public static void main(String[] args) {
ThirdThread rt = new ThirdThread();
//使用Lambda表达式创建Callable对象
//使用FuturTask包装Callable对象
FutureTask<Integer> task = new FutureTask<>((Callable<Integer>) ()->{
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值 " + i);
}
return i;
});
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值 " + i);
if (i == 20) {
new Thread(task, "有返回值的线程:").start();
}
}
try {
System.out.println("子线程返回值:" + task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
2. 线程的生命周期
线程的5种状态:新建(new)-> 就绪(Runnable)-> 运行(Running)-> 阻塞(Blocked)-> 死亡(Dead)
线程启动使用start()方法,而不是run方法。永远不要调用线程对象的run方法!!!否则,系统会将线程对象当作普通对象而不是线程对象。线程对象调用start方法后,只是表示该线程处于可以运行的状态,至于何时运行,取决于JVM的调度。start方法只能在新建状态后调用,否则会引发异常。start()方法的实现依赖于本地方法start0()方法,JNI技术(Java Native Interface)。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0(); //只声明了native方法,没有实现,交由本地jvm实现
3. 控制线程的方法
(1)join()
让一个线程等待另一个线程完成的方法。当在程序A中调用线程B的join方法时,A的线程被阻塞,直到B执行完才会执行A。
public class JoinThread extends Thread {
public JoinThread(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
new JoinThread("new Thread").start();
for (int i = 0; i < 100; i++) {
if ( i == 20 ) {
JoinThread jt = new JoinThread("join Thread");
jt.start();
jt.join(); //必须等jt执行结束才会向下执行,此时main线程被阻塞,join Thread和new Thread交替执行
}
//join Thread执行完毕,继续执行main线程
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
(2)后台线程
在后台运行,为其他线程提供服务。如果所有的前台线程都死亡,后台线程会自动死亡。
调用Thread对象的setDaemon(true)方法可将制定线程设置成后台线程。
(3)sleep(long millis):让当前正在执行的线程阻塞millis 毫秒。
(4)yeild():Thread类提供的静态方法,暂停当前线程,但不是进入阻塞状态,而是进入就绪状态,等待系统的线程调度器重新调度。sleep方法比yield方法有更好的可移植性,通常不建议使用yield方法来控制并发线程的执行。
(5)改变线程优先级
优先级高的线程获得较多的执行机会。每个线程默认的优先级与创建它的父线程优先级相同。默认情况下,main线程具有普通优先级,其创建的子线程也具有普通优先级。
设置优先级:Thread.currentThread().setPriority(int num); //num为优先级,范围1~10
获取优先级:Thread.currentThread().getPriority();
Thread类的三个静态常量:
MAX_PRIORITY: 10
MIN_PRIORITY: 1
NORM_PRIORITY: 5
4. 线程同步
1. 同步代码块
public class DrawThread extends Thread
{
// 模拟用户账户
private Account account;
// 当前取钱线程所希望取的钱数
private double drawAmount;
public DrawThread(String name , Account account
, double drawAmount)
{
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
// 当多条线程修改同一个共享数据时,将涉及数据安全问题。
public void run()
{
// 使用account作为同步监视器,任何线程进入下面同步代码块之前,
// 必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它
// 这种做法符合:“加锁 → 修改 → 释放锁”的逻辑
synchronized (account)
{
// 账户余额大于取钱数目
if (account.getBalance() >= drawAmount)
{
// 吐出钞票
System.out.println(getName()
+ "取钱成功!吐出钞票:" + drawAmount);
try
{
Thread.sleep(1);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
// 修改余额
account.setBalance(account.getBalance() - drawAmount);
System.out.println("\t余额为: " + account.getBalance());
}
else
{
System.out.println(getName() + "取钱失败!余额不足!");
}
}
// 同步代码块结束,该线程释放同步锁
}
}
2. 同步方法
public class Account
{
// 封装账户编号、账户余额两个成员变量
private String accountNo;
private double balance;
public Account(){}
// 构造器
public Account(String accountNo , double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
// accountNo的setter和getter方法
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
// 因此账户余额不允许随便修改,所以只为balance提供getter方法,
public double getBalance()
{
return this.balance;
}
// 提供一个线程安全draw()方法来完成取钱操作
public synchronized void draw(double drawAmount)
{
// 账户余额大于取钱数目
if (balance >= drawAmount)
{
// 吐出钞票
System.out.println(Thread.currentThread().getName()
+ "取钱成功!吐出钞票:" + drawAmount);
try
{
Thread.sleep(1);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
// 修改余额
balance -= drawAmount;
System.out.println("\t余额为: " + balance);
}
else
{
System.out.println(Thread.currentThread().getName()
+ "取钱失败!余额不足!");
}
}
// 下面两个方法根据accountNo来重写hashCode()和equals()方法
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if(this == obj)
return true;
if (obj !=null
&& obj.getClass() == Account.class)
{
Account target = (Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
3. 同步锁
public class Account
{
// 定义锁对象
private final ReentrantLock lock = new ReentrantLock();
// 封装账户编号、账户余额的两个成员变量
private String accountNo;
private double balance;
public Account(){}
// 构造器
public Account(String accountNo , double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
// accountNo的setter和getter方法
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
// 因此账户余额不允许随便修改,所以只为balance提供getter方法,
public double getBalance()
{
return this.balance;
}
// 提供一个线程安全draw()方法来完成取钱操作
public void draw(double drawAmount)
{
// 加锁
lock.lock();
try
{
// 账户余额大于取钱数目
if (balance >= drawAmount)
{
// 吐出钞票
System.out.println(Thread.currentThread().getName()
+ "取钱成功!吐出钞票:" + drawAmount);
try
{
Thread.sleep(1);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
// 修改余额
balance -= drawAmount;
System.out.println("\t余额为: " + balance);
}
else
{
System.out.println(Thread.currentThread().getName()
+ "取钱失败!余额不足!");
}
}
finally
{
// 修改完成,释放锁
lock.unlock();
}
}
// 下面两个方法根据accountNo来重写hashCode()和equals()方法
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if(this == obj)
return true;
if (obj !=null
&& obj.getClass() == Account.class)
{
Account target = (Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}