1.线程和进程
进程和线程的例子很多,因为是学习笔记,那就说一种我自己感觉很好理解的,就是我们天天使用的QQ,但我们运行QQ.exe程序时,进程就开始,我们可以同时打开对个聊天窗口,可以多人视频过,甚至可以一边视频一边手动聊天(可能音响坏了吧 。。。),其中每一个任务完全可以理解成是“线程”在工作,传音乐,图片表情,传文件等功能都有对应的线程在后台默默运行。
2使用多线程
2.1.1 一个类怎么就能变成“线程”了呢?
在回答上面的问题之前;我们首先要认识一个类和一个借口,Thread类和Runnable借口。实现多线程编程的方法有两种
1)继承Thread类(java的特点就是单继承,如果一个类既要继承别的类,还想实现线程,那么用Thread类肯定做不到)
2)实现Runnable借口(支持“多继承”就靠Runnable借口了,一边实现一边继承,但不过是那种方法创建的线程工作的性质是一样的)
继承Thread类
让我们先来看看源码Thread类的结构(其实Thread类也是实现的Runnable借口)
public class Thread implements Runnable
下面用一段简单的代码实现一个使用继承Thread类的线程
package test;
public class ThreadDemo01 {
public static void main(String[] args) {
TestThread tt=new TestThread();//创建线程对象
tt.start();//激活一个线程
for(int i=0;i<5;i++){
System.out.println("main线程正在运行");
try {
Thread.sleep(2000);//睡眠2s
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
class TestThread extends Thread{
public void run(){//重写父类run方法
for(int i=0;i<5;i++){
System.out.println("TestThread正在运行");
try {
Thread.sleep(1000);//睡眠1秒
} catch (Exception ex) {
// TODO: handle exception
ex.printStackTrace();
}
}
}
}
从上面的代码需要注意的是:1)进程Thread类的类必须从写run方法 。2)激活一个线程使用的是Start方法(实际是改变了线程的状态)
3)如果你多运行两遍这个程序你会发现结果不一样,这体现了多线程执行顺序是不确定的(主要看CPU时间片的长短)
实现Runnable借口
直接上代码
package test;
public class ThreadDemo02 {
public static void main(String[] args) {
TestThread tt=new TestThread();
new Thread(tt).start();//激活一个线程
for(int i=0;i<5;i++){
System.out.println("main正在运行");
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
class TestThread02 implements Runnable {
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("TestThread正在运行");
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
实现Runnable借口需要注意:1)我虽然实现了Runnable借口,但用的仍然是Thread类的Start方法激活线程(原因在于查看jdk文档得知Runnable只有一个run方法没有其他方法)2)Thread类的构造函数 public Thread(Runnable target)可以将借口的实现类对象作为参数,实例化出一个Thread类的对象
实例变量和线程安全
自定义线程类中的实例变量针对其他线程可以有共享和不共享之分。这也是多线程交互时很重要的知识点。
这里举一个买票的例子,火车票有很多的代受点,现在要求四个代受点共要卖出去5张票,
方案1 使用继承Thread类的方法
package test;
public class ThreadDemo03 {
public static void main(String[] args) {
TestThread03 t=new TestThread03();
//一个线程对象对象只能启动一个线程
t.start();
t.start();
t.start();
t.start();
}
}
class TestThread03 extends Thread{
private int i=5;
public void run(){
while(i>0){
System.out.println(Thread.currentThread().getName()+"出售票"+i);
i-=1;
}
}
}
执行一下会发现报了异常java.lang.IllegalThreadStateException这就说明这种写法不能起到“票源共享”,也说明一个对象只能启动一个线程
再来看方案2
package test;
public class ThreadDemo03 {
public static void main(String[] args) {
//TestThread03 t=new TestThread03();
new TestThread03().start();
new TestThread03().start();
new TestThread03().start();
new TestThread03().start();
}
}
class TestThread03 extends Thread{
private int i=5;
public void run(){
while(i>0){
System.out.println(Thread.currentThread().getName()+"出售票"+i);
i-=1;
}
}
}
从执行结果看是四个线程了,但票数不对了,这样看来是每个线程5张票,和我们的初衷4个线程共卖5张票严重不符,那么我们再来做一个小小的改动,将变量i设置成静态变量
方案3
package test;
public class ThreadDemo03 {
public static void main(String[] args) {
//TestThread03 t=new TestThread03();
new TestThread03().start();
new TestThread03().start();
new TestThread03().start();
new TestThread03().start();
}
}
class TestThread03 extends Thread{
private static int i=5;
public void run(){
while(i>0){
System.out.println(Thread.currentThread().getName()+"出售票"+i);
i-=1;
}
}
}
从执行的结果看出似乎完成了资源共享的要求,但是我们发现有的票出现了“一票多卖”的现象,这就非线程安全问题,这个稍后解决,先来看看使用Runnable借口怎么完成资源共享
运行如下代码
方案4
package test;
public class ThreadDemo03 {
public static void main(String[] args) {
TestThread03 t=new TestThread03();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestThread03 implements Runnable{
private int i=5;
public void run(){
while(i>0){
System.out.println(Thread.currentThread().getName()+"出售票"+i);
i-=1;
}
}
}
从方案4和方案3对比来看 使用Runnable借口似乎比使用Thread类有优势
现在讨论一下非线程安全问题
前面的方案3和方案4解决了资源共享问题,但出现了多个线程共卖一张票的问题(原因:假设票数现在是5,线程1看见了,执行了run方法,本应票数减1但是这时线程1的CPU时间片到了,必须退出,换别的线程,这时线程2看见的仍然是i=5的票数,于是它有卖了一遍)
要想解决这个问题实现一票一买必须要引用关键字synchronized(使。。。同步)
同不代码块的语法格式:
synchronized(对象){
需要同步的代码
}
还是先执行代码
package test;
public class ThreadDemo04 {
public static void main(String[] args) {
TestThread04 t=new TestThread04();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestThread04 implements Runnable{
private int i =5;
@Override
public void run() {
while(true){
synchronized (this) {
if(i<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"出售票"+i);
i-=1;
}
}
}
}
使用synchronized代码块的目的就是保证其中内容的原子性(要么不执行,要执行就一定执行完,不存在执行一半中断的情况)
且同一时刻进入临界区(synchronized代码块的)只能有一个线程
不光能使用代码块可也是用synchronized修饰的方法来解决“非线程安全问题”
执行如下代码:
package test;
public class ThreadDemo06 {
public static void main(String[] args) {
TestThread06 t=new TestThread06();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestThread06 implements Runnable{
private int i=5;
@Override
public void run() {
while(i>0){
sale();
}
}
public synchronized void sale(){
if(i>0)
System.out.println(Thread.currentThread().getName()+"出售票"+i);
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
i-=1;
}
}
待续。。媳妇让我睡觉