每天学习一道《剑指offer》的题目
剑指offer第一道题:
在一个二维数组中(每一个数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序,请完成一个函数,输入这样的一个二维数组和一个证书,判断数组中是否有该整数。
//上下左右有序的二维数组中查找数
public class Offer1 {
public static void main(String[] args) {
int[][] a = {{1,2,3,4},{2,6,8,9},{3,7,9,10},{10,23,45,66}};
boolean b = search(66, a);
System.out.println(b);
}
public static boolean search(int target ,int[][] a){
int lines = a.length;
int cols = a[0].length;
int x = 0;
int y = cols -1;
//x,y不越界
while (x<lines&&y>=0){
//如果左上角的值小于目标值就x++
if (a[x][y]<target){
x++;
//如果左上角的值小于目标值就x++
}else if(a[x][y]>target){
y--;
}else{
//否则就等于
return true;
}
}
return false;
}
}
线程的基本方法
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等。
线程等待(wait)
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方法一般用在同步方法或同步代码块中。
线程睡眠(sleep)
sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态
线程让步(yield)
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感
Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。
线程唤醒(notify)
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
线程共享数据卖票问题
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
public static void main(String[] args) {
Thread1 t1 = new Thread1();
new Thread(t1,"A").start();
new Thread(t1,"B").start();
}
}
class Thread1 implements Runnable{
static int chePiao = 1000;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (chePiao>0){
System.out.println(Thread.currentThread().getName()+"窗口卖出一张票"+"余票"+--chePiao);
}else {
break;
}
}finally {
lock.unlock();
}
}
}
}
生产者与消费者
/*
生产者与消费者
*/
public class TestConsumeAndProductor {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Product product = new Product(clerk);
Consume consume = new Consume(clerk);
new Thread(product,"生产者A").start();
new Thread(consume,"消费者B").start();
new Thread(product,"生产者C").start();
new Thread(consume,"消费者D").start();
}
}
//店员
class Clerk{
private int product=0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//进货
public void get(){
lock.lock();
try{
while (product>=1){
System.out.println("货已满");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"已进货货物,还剩"+ ++product);
condition.signalAll();
}finally {
lock.unlock();
}
}
//卖货
public void sale(){
lock.lock();
try{
while (product<=0){
System.out.println("货已空");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"卖出货物,还剩"+ --product);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
//生产者
class Product implements Runnable{
private Clerk clerk ;
public Product(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
clerk.get();
}
}
}
//消费者
class Consume implements Runnable{
private Clerk clerk;
public Consume(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
clerk.sale();
}
}
}
CountDownLatch(线程计数器 )
CountDownLatch 类位于 java.util.concurrent 包下,利用它可以实现类似计数器的功能。比如有一个任务 A,它要等待其他 4 个任务执行完毕之后才能执行,此时就可以利用 CountDownLatch
来实现这种功能了
//利用CountDownLatch来计算5个线程的执行所消耗的时间
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(5);
Test test = new Test(latch);
long start = System.currentTimeMillis();
for (int i = 0; i <5 ; i++) {
new Thread(test).start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("耗时毫秒:"+(end-start)+"mm");
}
}
class Test implements Runnable{
CountDownLatch latch ;
public Test(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
synchronized (this){
try{
for (int i = 0; i <10000 ; i++) {
System.out.println(i);
}
}finally {
latch.countDown();
}
}
}
}
内存可见性问题
Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,并且线程只能访问自己的工作内存,不可以访问其它线程的工作内存。工作内存中保存了主内存中共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中
———————————————— 版权声明:本文为CSDN博主「yZzc_XQ」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43262571/article/details/106572506
内存可见性:(Memory Visibility)是指当某个线程正在使用对象状态,而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true){
if(td.isFlag()){
System.out.println("------------------");
break;
}
}
}
}
class ThreadDemo implements Runnable {
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
flag = true;
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
//输出:
//flag=true
volatile的特点
1.保证了变量的内存可见性问题
2.不保证原子性问题
3.局部阻止了内存指令重排
//如果设置为 private volatile boolean flag = false;
//输出结果: flag=true
原子变量
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
// private volatile int serialNumber = 0;
private AtomicInteger serialNumber = new AtomicInteger(0);
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.print(getSerialNumber()+" ");
}
public int getSerialNumber(){
return serialNumber.getAndIncrement();
}
}
//运行结果
//1 3 2 0 4 6 5 7 8 9 ——> 不会重复
————————————————
版权声明:本文为CSDN博主「yZzc_XQ」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43262571/article/details/106572506
利用AtomicInteger可以保证原子性问题,底层使用的时CAS算法
CAS算法
Compare And Swap (Compare And Exchange) / 自旋 / 自旋锁 / 无锁
CAS 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。
CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:
需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V的值,否则不会执行任何操作
————————————————
版权声明:本文为CSDN博主「yZzc_XQ」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43262571/article/details/106572506
CAS算法也会引起ABA问题
比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。我们一班使用加版本号的方式解决ABA问题