目录
方式二:同步方法:将操作共享数据的方法声明为synchronized
一、创建线程的两种方式
1.继承Thread类
1) 定义子类继承Thread类。
2) 子类中重写Thread类中的run方法。
3) 创建Thread子类对象,即创建了线程对象。
4) 调用线程对象start方法:启动线程,调用run方法。
//方式一:继承于Thread类
class PrintNum extends Thread{
public void run(){
//子线程执行的代码
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public PrintNum(String name){
super(name);
}
}
public class TestThread {
public static void main(String[] args) {
PrintNum p1 = new PrintNum("线程1");
PrintNum p2 = new PrintNum("线程2");
p1.setPriority(Thread.MAX_PRIORITY);//10
p2.setPriority(Thread.MIN_PRIORITY);//1
p1.start();
p2.start();
}
}
2. 实现Runnable接口
1)定义子类,实现Runnable接口。
2)子类中重写Runnable接口中的run方法。
3)通过Thread类含参构造器创建线程对象。
4)将Runnable接口的子类对象作为实际参数传递给
Thread类的构造方法中。
5)调用Thread类的start方法:开启线程,调用
Runnable子类接口的run方法。
//方式二:实现Runnable接口
class SubThread implements Runnable{
public void run(){
//子线程执行的代码
for(int i = 1;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class TestThread{
public static void main(String[] args){
SubThread s = new SubThread();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
【区别】
继承Thread: 线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
【实现方法的好处】
1)避免了单继承的局限性
2)多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
二、Thread类的有关方法
void start(): 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
static currentThread(): 返回当前线程
static void yield():线程让步
Ø暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
Ø若队列中没有同优先级的线程,忽略此方法
join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
低优先级的线程也可以获得执行
static void sleep(long millis):(指定时间:毫秒)
Ø令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
stop(): 强制线程生命期结束
boolean isAlive():返回boolean,判断线程是否还活着
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级,线程创建时继承父线程的优先级
三、线程的生命周期
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止
四、线程的同步
前提:
如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题。
解决方式:
要求一个线程操作共享数据时,只有当其完成操作完成共享数据,其它线程才有机会执行共享数据。
方式一:同步代码块:
synchronized(同步监视器){
//操作共享数据的代码
}
注: 1.同步监视器:俗称锁,任何一个类的对象都可以才充当锁。要想保证线程的安全,必须要求所有的线程共用同一把锁!
2.使用实现Runnable接口的方式创建多线程的话,同步代码块中的锁,可以考虑是this。如果使用继承Thread类的方式,慎用this!
3.共享数据:多个线程需要共同操作的变量。 明确哪部分是操作共享数据的代码。
方式二:同步方法:将操作共享数据的方法声明为synchronized
比如:public synchronized void show(){ //操作共享数据的代码}
注:1.对于非静态的方法而言,使用同步的话,默认锁为:this。如果使用在继承的方式实现多线程的话,慎用!
2.对于静态的方法,如果使用同步,默认的锁为:当前类本身。以单例的懒汉式为例。 Class clazz = Singleton.class
总结:释放锁:wait();
不释放锁: sleep() yield() suspend() (过时,可能导致死锁)
死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
死锁是我们在使用同步时,需要避免的问题!
class Account {
double money;
public synchronized void changeMoney(double change) {
this.money += change;
// 放大线程安全问题
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + money);
}
}
class Custom {
Account account;
public Custom(Account account) {
super();
this.account = account;
}
public void saveMoney() {
new Thread() {
public void run() {
for (int i = 0; i < 3; i++) {
account.changeMoney(1000);
}
};
}.start();
}
}
/**
* 银行有一个账户。有两个储户分别向同一个账户存3000元, 每次存1000,存3次。每次存完打印账户余额。
*
* @author Administrator
*/
public class TestThread {
public static void main(String[] args) {
Account account = new Account();
Custom cust1 = new Custom(account);
Custom cust2 = new Custom(account);
cust1.saveMoney();
cust2.saveMoney();
}
}
五、线程的通信
如下的三个方法必须使用在同步代码块或同步方法中!
wait():当在同步中,执行到此方法,则此线程“等待”,直至其他线程执行notify()的方法,将其唤醒,唤醒后继续其wait()后的代码
notify()/notifyAll():在同步中,执行到此方法,则唤醒其他的某一个或所有的被wait的线程。
>例题:1.两个线程交替打印1-100自然数 2.生产者、消费者的例子
class Clerk {
int capacity;
public synchronized void addCapacity() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(capacity >= 20) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
capacity++;
System.out.println(Thread.currentThread().getName()+":"+"生产了第"+capacity+"个产品");
notifyAll();
}
}
public synchronized void subCapacity() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(capacity <= 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+":"+"消费了第"+capacity+"个产品");
capacity--;
notifyAll();
}
}
}
class Product implements Runnable {
Clerk clerk;
public Product(Clerk clerk) {
super();
this.clerk = clerk;
}
// 生产
@Override
public void run() {
while(true) {
clerk.addCapacity();
}
}
}
class Custom implements Runnable {
Clerk clerk;
public Custom(Clerk clerk) {
super();
this.clerk = clerk;
}
// 消费
@Override
public void run() {
while(true) {
clerk.subCapacity();
}
}
}
public class ProductAndCustom {
/**
* @param args
*/
public static void main(String[] args) {
Clerk clerk = new Clerk();
Product product = new Product(clerk);
Custom custom = new Custom(clerk);
Thread t1 = new Thread(product);
Thread t2 = new Thread(custom);
t1.setName("生产者1");
t2.setName("消费者1");
t1.start();
t2.start();
}
}