第一个问题,什么是线程?
线程是程序执行流的最小单元。
第二个问题,线程和进程的区别
进程是资源分配的基本单位,与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
我的理解呢,就好比进程是一个部门,线程是部门里面的工人。
========
线程例子一
★带互斥的共享栈
多线程互斥共享“栈”资源
首先写一个 栈
package cn.hncu.thread.sharedStack2;
public class MyStack {
// 假设 栈 为6个字符大小的
private char [] data = new char [6];
// 指针 ,当要往栈里面添加或者 取出字符时候都需要这个指针
private int idx =0;
// 向栈里面存字符的函数
public void Push(char c){
data[idx] = c;
idx ++;
System.out.println("存一个字符"+c);
}
// 向栈里面取字符的函数
public char Pop(){
idx--;
System.out.println("存一个字符"+data[idx]);
return data[idx];
}
}
然后 一个存入字符的线程 一个取出字符的线程
package cn.hncu.thread.sharedStack2;
// 线程的额写法一:继承Thread 然后写run方法,通过多态来调用我们的run方法
// 线程的额写法二:实现Runnable接口
public class PushThread extends Thread{
private MyStack Stack = null;
public PushThread(MyStack Stack){
this.Stack = Stack;
}
@Override
public void run(){
for (int i=97; i <103; i++) {
Stack.Push( (char)i );
}
}
}
package cn.hncu.thread.sharedStack2;
import cn.hncu.thread.sharedStack2.MyStack;
public class PopThread extends Thread{
private MyStack ms=null;
public PopThread(MyStack ms) {
this.ms = ms;
}
@Override
public void run() {
for(int i=97;i<103;i++){
ms.Pop();
}
}
}
结果,挂了
首先讲一下运行过程吧。
一开始Java虚拟机运行的是main函数,运行完t1.start()后,就有两个线程在抢资源,当t1抢到就运行ti里面的函数,我们这里是Push,存入字符。值得注意的是t1抢到并不代表t1一定会全部运行完。可能会是 运行一部分,cpu的资源就被其他的线程抢过去了,main 也相当于一个线程,main 当然是运行下一句 t2.start().之后就是t1,t2两个线程抢资源。当t2,比t1运行的多时,就可能出现数组下表越界。 那么怎么办呢?
加对象锁 谁拿到锁 谁就能运行。代码如下
把两个方法锁了 都是 锁this对象
package cn.hncu.thread.sharedStack2;
public class MyStack {
// 假设 栈 为6个字符大小的
private char [] data = new char [6];
// 指针 ,当要往栈里面添加或者 取出字符时候都需要这个指针
private int idx =0;
// 向栈里面存字符的函数
public void Push(char c){
// 对象锁只能锁 对象。
synchronized (this) {
data[idx] = c;
idx++;
System.out.println("存一个字符" + c);
}
}
// 向栈里面取字符的函数
// 当锁方法时,就相当于锁的是 方法所在的对象 就相当于前面的this
public synchronized char Pop(){
idx--;
System.out.println("取一个字符"+data[idx]);
return data[idx];
}
}
结果还是挂了
因为当‘取‘的线程 运行比’存‘的线程快时,还没存就取了,所以就又挂了。 那怎么办?
取的太快了就等一下 加了两个方法 this.wait(); this.notify();
package cn.hncu.thread.sharedStack2;
public class MyStack {
// 假设 栈 为6个字符大小的
private char [] data = new char [6];
// 指针 ,当要往栈里面添加或者 取出字符时候都需要这个指针
private int idx =0;
// 向栈里面存字符的函数
public void Push(char c){
// 对象锁只能锁 对象。
synchronized (this) {
data[idx] = c;
idx++;
System.out.println("存一个字符" + c);
// 这边叫一下
this.notify();
}
}
// 向栈里面取字符的函数
// 当锁方法时,就相当于锁的是 方法所在的对象 就相当于前面的this
public synchronized char Pop(){
if(idx<0){
try {
// 这边等一下
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
idx--;
System.out.println("取一个字符"+data[idx]);
return data[idx];
}
}
这个时候就好了
**说一下 Thread.sleep(x) 和 this.wait(),的区别
两个都会释放cpu 资源,sleep 不会释放锁,wait 会释放锁。**
线程例子二
多线程互斥共享“基本数据类型数据”资源
多个窗口买票,
票200张1~200号为整数类型,多个窗口同时买,肯定不能 甲窗口卖了 1号票 乙窗口又来卖1号票 所以就要给票加锁,票是正行,而我们的锁只能锁对象怎么办呢? 两个方法
v1 版本 把 票声明为静态的,同时声明一个静态的对象,把那个静态的对象锁了,就相当于把类模板上面的票锁了:
package cn.hncu.thread.ticket.v1;
public class TicketWin implements Runnable{
private String name=null;
//基本数据类型变量不能当对象锁,我们可以造一个与它平行的对象来代替它当锁
private static int num=200;
private static Object obj=new Object();
public TicketWin(String name) {
this.name=name;
}
@Override
public void run() {
synchronized ( obj ) { //对象锁 ----注意,这里不能用this
while (true) {
if (num == 0) {
break;
}
System.out.println(num--);
}
}
}
}
调用和结果
package cn.hncu.thread.ticket.v1;
public class SaleTicket {
public static void main(String[] args) {
Thread t1 = new Thread( new TicketWin("窗口1") );
Thread t2 = new Thread( new TicketWin("窗口2") );
Thread t3 = new Thread( new TicketWin("窗口3") );
Thread t4 = new Thread( new TicketWin("窗口4") );
t1.start();
t2.start();
t3.start();
t4.start();
}
}
v2 版本 把票所在的那个内的对象锁了,所有线程调用时,用同一个对象。就像书店系统里面的mainframe 一样:
package cn.hncu.thread.ticket.v2;
public class TicketWin implements Runnable{
private int num=200;
//private Object obj=new Object();//可以造一个平行的对象来代替,当作锁。但非静态变量当锁时,通常直接用this即可
@Override
public void run() {
synchronized (this) {//obj可以,但用this更简便
while (true) {
if (num == 0) {
break;
}
System.out.println(num--);
}
}
}
package cn.hncu.thread.ticket.v2;
public class SaleTicket {
public static void main(String[] args) {
TicketWin tickets = new TicketWin();
Thread t1 = new Thread( tickets );
Thread t2 = new Thread( tickets );
Thread t3 = new Thread( tickets );
Thread t4 = new Thread( tickets );
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果也是没有问题的
总结一下:
对于基本数据类型上锁,的两种方法。1.把基本数据类型声明为静态的放到类模板上。再在类模板上声明一个对象,把这个对象锁了就好。2. 就是所有的线程调用同一个对象,把这个对象锁了就行了。