实现多线程的第一种方式:继承Thread类
步骤:
一、写一个类继承Thread类
二、重写run()方法,run方法中写此线程执行的代码
三、在main方法中new此类对象,调用start方法
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<99;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
}
public class Note1 {
public static void main(String[] args) {
//给主方法设置线程名字
Thread.currentThread().setName("Main线程");
//新建两个线程
MyThread myThread=new MyThread();
MyThread myThread1=new MyThread();
//给新建的线程设置名字
myThread.setName("MyThread");
myThread1.setName("第二个线程对象");
//新建线程就绪,注意这个时候线程并未执行,当它强到CPU执行权时才执行
myThread.start();
myThread1.start();
for (int i=0;i<99;i++){
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
}
通过上面的代码可以发现,用这个方式新建线程的话,继承Thread的类就是线程类,它的对象就是具体的线程,
不同的线程有不同的对象,对象之间的非静态属性不共享数据。
总结:一个继承Thread的类,多个对象,多个线程,线程之间不共享非静态数据
实现多线程的第二种方式:实现Runnable接口
步骤:
第一步:写一个类实现Runnable接口
第二步:实现run方法
第三步:主方法中new此实现类
第四步:将此对象作为参数传给Thread的构造器形成线程对象
第五步:线程对象start
class RunThread implements Runnable{
@Override
public void run() {
for(int i=0;i<99;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
}
public class Note2 {
public static void main(String[] args) {
RunThread runThread=new RunThread();
Thread thread1=new Thread(runThread);
Thread thread2=new Thread(runThread);
thread1.start();
thread2.start();
}
}
通过上面的代码可以发现,用这个方式新建线程的话,实现Runnable的类的对象是作为参数传给Thread的
不同的线程可以有相同的对象,不同的线程之间可以共享数据
总结:一个实现Runnable的类,一个对象,多个线程,线程之间共享数据
实现多线程的第三种方式-实现Callabel接口(了解,日后补充)
步骤:
一、写一个类实现Callable接口
二、重写run方法
三、在主方法中new对象
四、此对象传给FutureTask的构造器,new一个FutureTasl的对象
五、将FutureTask对象传给Thread的构造器,new一个Thread对象
六、Thread对象调用start方法
class CallThread implements Callable{
@Override
public Object call() throws Exception {
int sum=0;
for(int i=0;i<=100;i++){
sum+=i;
System.out.println(Thread.currentThread().getName()+": "+i);
}
return sum;
}
}
public class Note3 {
public static void main(String[] args) {
CallThread callThread=new CallThread();
FutureTask futureTask=new FutureTask(callThread);
FutureTask futureTask1=new FutureTask(callThread);
Thread thread=new Thread(futureTask);
Thread thread1=new Thread(futureTask1);
thread.start();
thread1.start();
}
}
实现多线程的第四种方式-线程池(了解,后面再补充)
线程的同步
问题:三个窗口总共有100个门票,用多线程实现(提示:一个窗口一个线程)
方法一:用继承Thread类的方法新建多线程,三个对象分别代表三个窗口。由于票数是固定的,三个窗口共享的,所以将票的数目声明为了类变量。下面的代码是没有考虑线程安全的问题,为了让线程安全的问题更加逼真,在run方法中使用了让不同的对象阻塞1秒的方法。(注意,Thread类里有sleep方法,this.sleep()调用的不是Object类的sleep,所以休眠的是线程)
class Window extends Thread{
public static int ticketNum=100;
@Override
public void run() {
for(;ticketNum>=0;ticketNum--){
System.out.println(Thread.currentThread().getName()+"窗口卖第"+ticketNum+"号票");
try {
this.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Note4 {
public static void main(String[] args) {
Window window1=new Window();
window1.setName("Window1");
Window window2=new Window();
window2.setName("Window2");
window1.start();
window2.start();
}
}
会发现出现了上面的问题,问题的原因就是:
不同的线程处理共享的数据时,会出现线程安全问题。这里由于一个线程在打印语句结束后,tickNum并没有立马减1,而是先休眠了一秒钟,在这一秒钟里,另外一个线程打印语句执行时ticket还是没用减1时的状态,所以出现了问题。
方法2:用实现Runnable接口的方法创造线程对象
注意,这里ticketNum可以是非静态属性
class RunWindow implements Runnable{
private int ticketNum=100;
@Override
public void run() {
for(;ticketNum>=0;ticketNum--){
System.out.println(Thread.currentThread().getName()+"窗口卖第"+ticketNum+"号票");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Note5 {
public static void main(String[] args) {
RunWindow runWindow=new RunWindow();
Thread thread1=new Thread(runWindow);
thread1.setName("Window1");
Thread thread2=new Thread(runWindow);
thread2.setName("Window2");
thread1.start();
thread2.start();
}
}
这也是出现了问题,问题的原因同方法一一样
方法一的解决
synchronized (Window.class){
for(;ticketNum>=0;ticketNum--){
System.out.println(Thread.currentThread().getName()+"窗口卖第"+ticketNum+"号票");
try {
this.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized的参数是同步监视器,这个同步监视器必须是唯一的,Thread继承创建的线程对象是多个,对象不唯一,所以不能用this
方法二的解决
public void run() {
synchronized (this) {
for (; ticketNum >= 0; ticketNum--) {
System.out.println(Thread.currentThread().getName() + "窗口卖第" + ticketNum + "号票");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这个方法的同步监视器是this
总结线程安全问题:
当多个线程处理共享数据(堆内存中的数据)时,就要考虑线程安全问题
对于方法一,它的共享数据是静态数据
对于方法二,它的共享数据是属性和非静态数据
解决线程安全问题的思路:用同步代码块将涉及到共享数据的代码括起来
对于方法一,锁是类.class
对于方法二,锁是this
噢,时间太晚了,明天总结线程生命周期以及控制方法。
synchronized (this) {
for (this.i = 0; this.i< 100; ) {
i++;
this.notify();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " :" + i);
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}